LCOV - code coverage report
Current view: top level - frmts/wmts - wmtsdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1141 1259 90.6 %
Date: 2025-10-27 00:14:23 Functions: 30 31 96.8 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL WMTS driver
       4             :  * Purpose:  Implement GDAL WMTS support
       5             :  * Author:   Even Rouault, <even dot rouault at spatialys dot com>
       6             :  * Funded by Land Information New Zealand (LINZ)
       7             :  *
       8             :  **********************************************************************
       9             :  * Copyright (c) 2015, Even Rouault <even dot rouault at spatialys dot com>
      10             :  *
      11             :  * SPDX-License-Identifier: MIT
      12             :  ****************************************************************************/
      13             : 
      14             : #include "cpl_http.h"
      15             : #include "cpl_minixml.h"
      16             : #include "gdal_frmts.h"
      17             : #include "gdal_pam.h"
      18             : #include "ogr_spatialref.h"
      19             : #include "../vrt/gdal_vrt.h"
      20             : #include "wmtsdrivercore.h"
      21             : 
      22             : #include <algorithm>
      23             : #include <array>
      24             : #include <cmath>
      25             : #include <map>
      26             : #include <set>
      27             : #include <vector>
      28             : #include <limits>
      29             : 
      30             : // g++ -g -Wall -fPIC frmts/wmts/wmtsdataset.cpp -shared -o gdal_WMTS.so -Iport
      31             : // -Igcore -Iogr -Iogr/ogrsf_frmts -L. -lgdal
      32             : 
      33             : /* Set in stone by WMTS spec. In pixel/meter */
      34             : #define WMTS_PITCH 0.00028
      35             : 
      36             : #define WMTS_WGS84_DEG_PER_METER (180 / M_PI / SRS_WGS84_SEMIMAJOR)
      37             : 
      38             : typedef enum
      39             : {
      40             :     AUTO,
      41             :     LAYER_BBOX,
      42             :     TILE_MATRIX_SET,
      43             :     MOST_PRECISE_TILE_MATRIX
      44             : } ExtentMethod;
      45             : 
      46             : /************************************************************************/
      47             : /* ==================================================================== */
      48             : /*                            WMTSTileMatrix                            */
      49             : /* ==================================================================== */
      50             : /************************************************************************/
      51             : 
      52             : class WMTSTileMatrix
      53             : {
      54             :   public:
      55             :     CPLString osIdentifier{};
      56             :     double dfScaleDenominator = 0;
      57             :     double dfPixelSize = 0;
      58             :     double dfTLX = 0;
      59             :     double dfTLY = 0;
      60             :     int nTileWidth = 0;
      61             :     int nTileHeight = 0;
      62             :     int nMatrixWidth = 0;
      63             :     int nMatrixHeight = 0;
      64             : 
      65         263 :     OGREnvelope GetExtent() const
      66             :     {
      67         263 :         OGREnvelope sExtent;
      68         263 :         sExtent.MinX = dfTLX;
      69         263 :         sExtent.MaxX = dfTLX + nMatrixWidth * dfPixelSize * nTileWidth;
      70         263 :         sExtent.MaxY = dfTLY;
      71         263 :         sExtent.MinY = dfTLY - nMatrixHeight * dfPixelSize * nTileHeight;
      72         263 :         return sExtent;
      73             :     }
      74             : };
      75             : 
      76             : /************************************************************************/
      77             : /* ==================================================================== */
      78             : /*                          WMTSTileMatrixLimits                        */
      79             : /* ==================================================================== */
      80             : /************************************************************************/
      81             : 
      82             : class WMTSTileMatrixLimits
      83             : {
      84             :   public:
      85             :     CPLString osIdentifier{};
      86             :     int nMinTileRow = 0;
      87             :     int nMaxTileRow = 0;
      88             :     int nMinTileCol = 0;
      89             :     int nMaxTileCol = 0;
      90             : 
      91           4 :     OGREnvelope GetExtent(const WMTSTileMatrix &oTM) const
      92             :     {
      93           4 :         OGREnvelope sExtent;
      94           4 :         const double dfTileWidthUnits = oTM.dfPixelSize * oTM.nTileWidth;
      95           4 :         const double dfTileHeightUnits = oTM.dfPixelSize * oTM.nTileHeight;
      96           4 :         sExtent.MinX = oTM.dfTLX + nMinTileCol * dfTileWidthUnits;
      97           4 :         sExtent.MaxY = oTM.dfTLY - nMinTileRow * dfTileHeightUnits;
      98           4 :         sExtent.MaxX = oTM.dfTLX + (nMaxTileCol + 1) * dfTileWidthUnits;
      99           4 :         sExtent.MinY = oTM.dfTLY - (nMaxTileRow + 1) * dfTileHeightUnits;
     100           4 :         return sExtent;
     101             :     }
     102             : };
     103             : 
     104             : /************************************************************************/
     105             : /* ==================================================================== */
     106             : /*                          WMTSTileMatrixSet                           */
     107             : /* ==================================================================== */
     108             : /************************************************************************/
     109             : 
     110             : class WMTSTileMatrixSet
     111             : {
     112             :   public:
     113             :     OGRSpatialReference oSRS{};
     114             :     CPLString osSRS{};
     115             :     bool bBoundingBoxValid = false;
     116             :     OGREnvelope sBoundingBox{}; /* expressed in TMS SRS */
     117             :     std::vector<WMTSTileMatrix> aoTM{};
     118             : 
     119         110 :     WMTSTileMatrixSet()
     120         110 :     {
     121         110 :         oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     122         110 :     }
     123             : };
     124             : 
     125             : /************************************************************************/
     126             : /* ==================================================================== */
     127             : /*                              WMTSDataset                             */
     128             : /* ==================================================================== */
     129             : /************************************************************************/
     130             : 
     131             : class WMTSDataset final : public GDALPamDataset
     132             : {
     133             :     friend class WMTSBand;
     134             : 
     135             :     CPLString osLayer{};
     136             :     CPLString osTMS{};
     137             :     CPLString osXML{};
     138             :     CPLString osURLFeatureInfoTemplate{};
     139             :     WMTSTileMatrixSet oTMS{};
     140             : 
     141             :     CPLStringList m_aosHTTPOptions{};
     142             : 
     143             :     std::vector<GDALDataset *> apoDatasets{};
     144             :     OGRSpatialReference m_oSRS{};
     145             :     GDALGeoTransform m_gt{};
     146             : 
     147             :     CPLString osLastGetFeatureInfoURL{};
     148             :     CPLString osMetadataItemGetFeatureInfo{};
     149             : 
     150             :     static CPLStringList BuildHTTPRequestOpts(CPLString osOtherXML);
     151             :     static CPLXMLNode *GetCapabilitiesResponse(const CPLString &osFilename,
     152             :                                                CSLConstList papszHTTPOptions);
     153             :     static CPLString FixCRSName(const char *pszCRS);
     154             :     static CPLString Replace(const CPLString &osStr, const char *pszOld,
     155             :                              const char *pszNew);
     156             :     static CPLString GetOperationKVPURL(CPLXMLNode *psXML,
     157             :                                         const char *pszOperation);
     158             :     static int ReadTMS(CPLXMLNode *psContents, const CPLString &osIdentifier,
     159             :                        const CPLString &osMaxTileMatrixIdentifier,
     160             :                        int nMaxZoomLevel, WMTSTileMatrixSet &oTMS,
     161             :                        bool &bHasWarnedAutoSwap);
     162             :     static int ReadTMLimits(
     163             :         CPLXMLNode *psTMSLimits,
     164             :         std::map<CPLString, WMTSTileMatrixLimits> &aoMapTileMatrixLimits);
     165             : 
     166             :   public:
     167             :     WMTSDataset();
     168             :     ~WMTSDataset() override;
     169             : 
     170             :     CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
     171             :     const OGRSpatialReference *GetSpatialRef() const override;
     172             :     virtual const char *GetMetadataItem(const char *pszName,
     173             :                                         const char *pszDomain) override;
     174             : 
     175             :     static GDALDataset *Open(GDALOpenInfo *);
     176             :     static GDALDataset *CreateCopy(const char *pszFilename,
     177             :                                    GDALDataset *poSrcDS, CPL_UNUSED int bStrict,
     178             :                                    CPL_UNUSED char **papszOptions,
     179             :                                    CPL_UNUSED GDALProgressFunc pfnProgress,
     180             :                                    CPL_UNUSED void *pProgressData);
     181             : 
     182             :   protected:
     183             :     int CloseDependentDatasets() override;
     184             : 
     185             :     CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize,
     186             :                      int nYSize, void *pData, int nBufXSize, int nBufYSize,
     187             :                      GDALDataType eBufType, int nBandCount,
     188             :                      BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
     189             :                      GSpacing nLineSpace, GSpacing nBandSpace,
     190             :                      GDALRasterIOExtraArg *psExtraArg) override;
     191             : };
     192             : 
     193             : /************************************************************************/
     194             : /* ==================================================================== */
     195             : /*                               WMTSBand                               */
     196             : /* ==================================================================== */
     197             : /************************************************************************/
     198             : 
     199             : class WMTSBand final : public GDALPamRasterBand
     200             : {
     201             :   public:
     202             :     WMTSBand(WMTSDataset *poDS, int nBand, GDALDataType eDataType);
     203             : 
     204             :     GDALRasterBand *GetOverview(int nLevel) override;
     205             :     int GetOverviewCount() override;
     206             :     GDALColorInterp GetColorInterpretation() override;
     207             :     virtual const char *GetMetadataItem(const char *pszName,
     208             :                                         const char *pszDomain) override;
     209             : 
     210             :   protected:
     211             :     CPLErr IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage) override;
     212             :     CPLErr IRasterIO(GDALRWFlag, int, int, int, int, void *, int, int,
     213             :                      GDALDataType, GSpacing, GSpacing,
     214             :                      GDALRasterIOExtraArg *psExtraArg) override;
     215             : };
     216             : 
     217             : /************************************************************************/
     218             : /*                            WMTSBand()                                */
     219             : /************************************************************************/
     220             : 
     221         188 : WMTSBand::WMTSBand(WMTSDataset *poDSIn, int nBandIn, GDALDataType eDataTypeIn)
     222             : {
     223         188 :     poDS = poDSIn;
     224         188 :     nBand = nBandIn;
     225         188 :     eDataType = eDataTypeIn;
     226         188 :     poDSIn->apoDatasets[0]->GetRasterBand(1)->GetBlockSize(&nBlockXSize,
     227             :                                                            &nBlockYSize);
     228         188 : }
     229             : 
     230             : /************************************************************************/
     231             : /*                            IReadBlock()                              */
     232             : /************************************************************************/
     233             : 
     234           0 : CPLErr WMTSBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
     235             : {
     236           0 :     WMTSDataset *poGDS = cpl::down_cast<WMTSDataset *>(poDS);
     237           0 :     return poGDS->apoDatasets[0]->GetRasterBand(nBand)->ReadBlock(
     238           0 :         nBlockXOff, nBlockYOff, pImage);
     239             : }
     240             : 
     241             : /************************************************************************/
     242             : /*                             IRasterIO()                              */
     243             : /************************************************************************/
     244             : 
     245          35 : CPLErr WMTSBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize,
     246             :                            int nYSize, void *pData, int nBufXSize,
     247             :                            int nBufYSize, GDALDataType eBufType,
     248             :                            GSpacing nPixelSpace, GSpacing nLineSpace,
     249             :                            GDALRasterIOExtraArg *psExtraArg)
     250             : {
     251          35 :     WMTSDataset *poGDS = cpl::down_cast<WMTSDataset *>(poDS);
     252             : 
     253          35 :     if ((nBufXSize < nXSize || nBufYSize < nYSize) &&
     254          70 :         poGDS->apoDatasets.size() > 1 && eRWFlag == GF_Read)
     255             :     {
     256             :         int bTried;
     257           1 :         CPLErr eErr = TryOverviewRasterIO(
     258             :             eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
     259             :             eBufType, nPixelSpace, nLineSpace, psExtraArg, &bTried);
     260           1 :         if (bTried)
     261           1 :             return eErr;
     262             :     }
     263             : 
     264          34 :     return poGDS->apoDatasets[0]->GetRasterBand(nBand)->RasterIO(
     265             :         eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
     266          34 :         eBufType, nPixelSpace, nLineSpace, psExtraArg);
     267             : }
     268             : 
     269             : /************************************************************************/
     270             : /*                         GetOverviewCount()                           */
     271             : /************************************************************************/
     272             : 
     273          15 : int WMTSBand::GetOverviewCount()
     274             : {
     275          15 :     WMTSDataset *poGDS = cpl::down_cast<WMTSDataset *>(poDS);
     276             : 
     277          15 :     if (poGDS->apoDatasets.size() > 1)
     278          13 :         return static_cast<int>(poGDS->apoDatasets.size()) - 1;
     279             :     else
     280           2 :         return 0;
     281             : }
     282             : 
     283             : /************************************************************************/
     284             : /*                              GetOverview()                           */
     285             : /************************************************************************/
     286             : 
     287          11 : GDALRasterBand *WMTSBand::GetOverview(int nLevel)
     288             : {
     289          11 :     WMTSDataset *poGDS = cpl::down_cast<WMTSDataset *>(poDS);
     290             : 
     291          11 :     if (nLevel < 0 || nLevel >= GetOverviewCount())
     292           1 :         return nullptr;
     293             : 
     294          10 :     GDALDataset *poOvrDS = poGDS->apoDatasets[nLevel + 1];
     295          10 :     if (poOvrDS)
     296          10 :         return poOvrDS->GetRasterBand(nBand);
     297             :     else
     298           0 :         return nullptr;
     299             : }
     300             : 
     301             : /************************************************************************/
     302             : /*                   GetColorInterpretation()                           */
     303             : /************************************************************************/
     304             : 
     305           4 : GDALColorInterp WMTSBand::GetColorInterpretation()
     306             : {
     307           4 :     WMTSDataset *poGDS = cpl::down_cast<WMTSDataset *>(poDS);
     308           4 :     if (poGDS->nBands == 1)
     309             :     {
     310           0 :         return GCI_GrayIndex;
     311             :     }
     312           4 :     else if (poGDS->nBands == 3 || poGDS->nBands == 4)
     313             :     {
     314           4 :         if (nBand == 1)
     315           1 :             return GCI_RedBand;
     316           3 :         else if (nBand == 2)
     317           1 :             return GCI_GreenBand;
     318           2 :         else if (nBand == 3)
     319           1 :             return GCI_BlueBand;
     320           1 :         else if (nBand == 4)
     321           1 :             return GCI_AlphaBand;
     322             :     }
     323             : 
     324           0 :     return GCI_Undefined;
     325             : }
     326             : 
     327             : /************************************************************************/
     328             : /*                         GetMetadataItem()                            */
     329             : /************************************************************************/
     330             : 
     331           8 : const char *WMTSBand::GetMetadataItem(const char *pszName,
     332             :                                       const char *pszDomain)
     333             : {
     334           8 :     WMTSDataset *poGDS = cpl::down_cast<WMTSDataset *>(poDS);
     335             : 
     336             :     /* ==================================================================== */
     337             :     /*      LocationInfo handling.                                          */
     338             :     /* ==================================================================== */
     339           8 :     if (pszDomain != nullptr && EQUAL(pszDomain, "LocationInfo") &&
     340           7 :         pszName != nullptr && STARTS_WITH_CI(pszName, "Pixel_") &&
     341          16 :         !poGDS->oTMS.aoTM.empty() && !poGDS->osURLFeatureInfoTemplate.empty())
     342             :     {
     343             :         int iPixel, iLine;
     344             : 
     345             :         /* --------------------------------------------------------------------
     346             :          */
     347             :         /*      What pixel are we aiming at? */
     348             :         /* --------------------------------------------------------------------
     349             :          */
     350           6 :         if (sscanf(pszName + 6, "%d_%d", &iPixel, &iLine) != 2)
     351           0 :             return nullptr;
     352             : 
     353           6 :         const WMTSTileMatrix &oTM = poGDS->oTMS.aoTM.back();
     354             : 
     355           6 :         iPixel += static_cast<int>(
     356           6 :             std::round((poGDS->m_gt[0] - oTM.dfTLX) / oTM.dfPixelSize));
     357           6 :         iLine += static_cast<int>(
     358           6 :             std::round((oTM.dfTLY - poGDS->m_gt[3]) / oTM.dfPixelSize));
     359             : 
     360          12 :         CPLString osURL(poGDS->osURLFeatureInfoTemplate);
     361           6 :         osURL = WMTSDataset::Replace(osURL, "{TileMatrixSet}", poGDS->osTMS);
     362           6 :         osURL = WMTSDataset::Replace(osURL, "{TileMatrix}", oTM.osIdentifier);
     363          12 :         osURL = WMTSDataset::Replace(osURL, "{TileCol}",
     364          12 :                                      CPLSPrintf("%d", iPixel / oTM.nTileWidth));
     365          12 :         osURL = WMTSDataset::Replace(osURL, "{TileRow}",
     366          12 :                                      CPLSPrintf("%d", iLine / oTM.nTileHeight));
     367          12 :         osURL = WMTSDataset::Replace(osURL, "{I}",
     368          12 :                                      CPLSPrintf("%d", iPixel % oTM.nTileWidth));
     369          12 :         osURL = WMTSDataset::Replace(osURL, "{J}",
     370          12 :                                      CPLSPrintf("%d", iLine % oTM.nTileHeight));
     371             : 
     372           6 :         if (poGDS->osLastGetFeatureInfoURL.compare(osURL) != 0)
     373             :         {
     374           5 :             poGDS->osLastGetFeatureInfoURL = osURL;
     375           5 :             poGDS->osMetadataItemGetFeatureInfo = "";
     376           5 :             char *pszRes = nullptr;
     377             :             CPLHTTPResult *psResult =
     378           5 :                 CPLHTTPFetch(osURL, poGDS->m_aosHTTPOptions.List());
     379           5 :             if (psResult && psResult->nStatus == 0 && psResult->pabyData)
     380           3 :                 pszRes = CPLStrdup(
     381           3 :                     reinterpret_cast<const char *>(psResult->pabyData));
     382           5 :             CPLHTTPDestroyResult(psResult);
     383             : 
     384           5 :             if (pszRes)
     385             :             {
     386           3 :                 poGDS->osMetadataItemGetFeatureInfo = "<LocationInfo>";
     387           3 :                 CPLPushErrorHandler(CPLQuietErrorHandler);
     388           3 :                 CPLXMLNode *psXML = CPLParseXMLString(pszRes);
     389           3 :                 CPLPopErrorHandler();
     390           3 :                 if (psXML != nullptr && psXML->eType == CXT_Element)
     391             :                 {
     392           1 :                     if (strcmp(psXML->pszValue, "?xml") == 0)
     393             :                     {
     394           1 :                         if (psXML->psNext)
     395             :                         {
     396           1 :                             char *pszXML = CPLSerializeXMLTree(psXML->psNext);
     397           1 :                             poGDS->osMetadataItemGetFeatureInfo += pszXML;
     398           1 :                             CPLFree(pszXML);
     399             :                         }
     400             :                     }
     401             :                     else
     402             :                     {
     403           0 :                         poGDS->osMetadataItemGetFeatureInfo += pszRes;
     404           1 :                     }
     405             :                 }
     406             :                 else
     407             :                 {
     408             :                     char *pszEscapedXML =
     409           2 :                         CPLEscapeString(pszRes, -1, CPLES_XML_BUT_QUOTES);
     410           2 :                     poGDS->osMetadataItemGetFeatureInfo += pszEscapedXML;
     411           2 :                     CPLFree(pszEscapedXML);
     412             :                 }
     413           3 :                 if (psXML != nullptr)
     414           3 :                     CPLDestroyXMLNode(psXML);
     415             : 
     416           3 :                 poGDS->osMetadataItemGetFeatureInfo += "</LocationInfo>";
     417           3 :                 CPLFree(pszRes);
     418             :             }
     419             :         }
     420           6 :         return poGDS->osMetadataItemGetFeatureInfo.c_str();
     421             :     }
     422             : 
     423           2 :     return GDALPamRasterBand::GetMetadataItem(pszName, pszDomain);
     424             : }
     425             : 
     426             : /************************************************************************/
     427             : /*                          WMTSDataset()                               */
     428             : /************************************************************************/
     429             : 
     430          55 : WMTSDataset::WMTSDataset()
     431             : {
     432          55 :     m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     433          55 : }
     434             : 
     435             : /************************************************************************/
     436             : /*                        ~WMTSDataset()                                */
     437             : /************************************************************************/
     438             : 
     439         110 : WMTSDataset::~WMTSDataset()
     440             : {
     441          55 :     WMTSDataset::CloseDependentDatasets();
     442         110 : }
     443             : 
     444             : /************************************************************************/
     445             : /*                      CloseDependentDatasets()                        */
     446             : /************************************************************************/
     447             : 
     448          55 : int WMTSDataset::CloseDependentDatasets()
     449             : {
     450          55 :     int bRet = GDALPamDataset::CloseDependentDatasets();
     451          55 :     if (!apoDatasets.empty())
     452             :     {
     453         147 :         for (size_t i = 0; i < apoDatasets.size(); i++)
     454         100 :             delete apoDatasets[i];
     455          47 :         apoDatasets.resize(0);
     456          47 :         bRet = TRUE;
     457             :     }
     458          55 :     return bRet;
     459             : }
     460             : 
     461             : /************************************************************************/
     462             : /*                             IRasterIO()                              */
     463             : /************************************************************************/
     464             : 
     465           2 : CPLErr WMTSDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
     466             :                               int nXSize, int nYSize, void *pData,
     467             :                               int nBufXSize, int nBufYSize,
     468             :                               GDALDataType eBufType, int nBandCount,
     469             :                               BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
     470             :                               GSpacing nLineSpace, GSpacing nBandSpace,
     471             :                               GDALRasterIOExtraArg *psExtraArg)
     472             : {
     473           2 :     if ((nBufXSize < nXSize || nBufYSize < nYSize) && apoDatasets.size() > 1 &&
     474             :         eRWFlag == GF_Read)
     475             :     {
     476             :         int bTried;
     477           1 :         CPLErr eErr = TryOverviewRasterIO(
     478             :             eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
     479             :             eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace,
     480             :             nBandSpace, psExtraArg, &bTried);
     481           1 :         if (bTried)
     482           1 :             return eErr;
     483             :     }
     484             : 
     485           1 :     return apoDatasets[0]->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
     486             :                                     pData, nBufXSize, nBufYSize, eBufType,
     487             :                                     nBandCount, panBandMap, nPixelSpace,
     488           1 :                                     nLineSpace, nBandSpace, psExtraArg);
     489             : }
     490             : 
     491             : /************************************************************************/
     492             : /*                          GetGeoTransform()                           */
     493             : /************************************************************************/
     494             : 
     495           9 : CPLErr WMTSDataset::GetGeoTransform(GDALGeoTransform &gt) const
     496             : {
     497           9 :     gt = m_gt;
     498           9 :     return CE_None;
     499             : }
     500             : 
     501             : /************************************************************************/
     502             : /*                         GetSpatialRef()                              */
     503             : /************************************************************************/
     504             : 
     505           9 : const OGRSpatialReference *WMTSDataset::GetSpatialRef() const
     506             : {
     507           9 :     return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
     508             : }
     509             : 
     510             : /************************************************************************/
     511             : /*                          WMTSEscapeXML()                             */
     512             : /************************************************************************/
     513             : 
     514         266 : static CPLString WMTSEscapeXML(const char *pszUnescapedXML)
     515             : {
     516         266 :     CPLString osRet;
     517         266 :     char *pszTmp = CPLEscapeString(pszUnescapedXML, -1, CPLES_XML);
     518         266 :     osRet = pszTmp;
     519         266 :     CPLFree(pszTmp);
     520         266 :     return osRet;
     521             : }
     522             : 
     523             : /************************************************************************/
     524             : /*                         GetMetadataItem()                            */
     525             : /************************************************************************/
     526             : 
     527           4 : const char *WMTSDataset::GetMetadataItem(const char *pszName,
     528             :                                          const char *pszDomain)
     529             : {
     530           4 :     if (pszName != nullptr && EQUAL(pszName, "XML") && pszDomain != nullptr &&
     531           2 :         EQUAL(pszDomain, "WMTS"))
     532             :     {
     533           2 :         return osXML.c_str();
     534             :     }
     535             : 
     536           2 :     return GDALPamDataset::GetMetadataItem(pszName, pszDomain);
     537             : }
     538             : 
     539             : /************************************************************************/
     540             : /*                          QuoteIfNecessary()                          */
     541             : /************************************************************************/
     542             : 
     543         121 : static CPLString QuoteIfNecessary(const char *pszVal)
     544             : {
     545         121 :     if (strchr(pszVal, ' ') || strchr(pszVal, ',') || strchr(pszVal, '='))
     546             :     {
     547          48 :         CPLString osVal;
     548          24 :         osVal += "\"";
     549          24 :         osVal += pszVal;
     550          24 :         osVal += "\"";
     551          24 :         return osVal;
     552             :     }
     553             :     else
     554          97 :         return pszVal;
     555             : }
     556             : 
     557             : /************************************************************************/
     558             : /*                             FixCRSName()                             */
     559             : /************************************************************************/
     560             : 
     561         104 : CPLString WMTSDataset::FixCRSName(const char *pszCRS)
     562             : {
     563         104 :     while (*pszCRS == ' ' || *pszCRS == '\r' || *pszCRS == '\n')
     564           0 :         pszCRS++;
     565             : 
     566             :     /* http://maps.wien.gv.at/wmts/1.0.0/WMTSCapabilities.xml uses
     567             :      * urn:ogc:def:crs:EPSG:6.18:3:3857 */
     568             :     /* instead of urn:ogc:def:crs:EPSG:6.18.3:3857. Coming from an incorrect
     569             :      * example of URN in WMTS spec */
     570             :     /* https://portal.opengeospatial.org/files/?artifact_id=50398 */
     571         104 :     if (STARTS_WITH_CI(pszCRS, "urn:ogc:def:crs:EPSG:6.18:3:"))
     572             :     {
     573             :         return CPLSPrintf("urn:ogc:def:crs:EPSG::%s",
     574          43 :                           pszCRS + strlen("urn:ogc:def:crs:EPSG:6.18:3:"));
     575             :     }
     576             : 
     577          61 :     if (EQUAL(pszCRS, "urn:ogc:def:crs:EPSG::102100"))
     578           0 :         return "EPSG:3857";
     579             : 
     580         122 :     CPLString osRet(pszCRS);
     581         122 :     while (osRet.size() && (osRet.back() == ' ' || osRet.back() == '\r' ||
     582          61 :                             osRet.back() == '\n'))
     583             :     {
     584           0 :         osRet.pop_back();
     585             :     }
     586          61 :     return osRet;
     587             : }
     588             : 
     589             : /************************************************************************/
     590             : /*                              ReadTMS()                               */
     591             : /************************************************************************/
     592             : 
     593          55 : int WMTSDataset::ReadTMS(CPLXMLNode *psContents, const CPLString &osIdentifier,
     594             :                          const CPLString &osMaxTileMatrixIdentifier,
     595             :                          int nMaxZoomLevel, WMTSTileMatrixSet &oTMS,
     596             :                          bool &bHasWarnedAutoSwap)
     597             : {
     598         110 :     for (CPLXMLNode *psIter = psContents->psChild; psIter != nullptr;
     599          55 :          psIter = psIter->psNext)
     600             :     {
     601         110 :         if (psIter->eType != CXT_Element ||
     602         110 :             strcmp(psIter->pszValue, "TileMatrixSet") != 0)
     603          55 :             continue;
     604          55 :         const char *pszIdentifier = CPLGetXMLValue(psIter, "Identifier", "");
     605          55 :         if (!EQUAL(osIdentifier, pszIdentifier))
     606           0 :             continue;
     607             :         const char *pszSupportedCRS =
     608          55 :             CPLGetXMLValue(psIter, "SupportedCRS", nullptr);
     609          55 :         if (pszSupportedCRS == nullptr)
     610             :         {
     611           1 :             CPLError(CE_Failure, CPLE_AppDefined, "Missing SupportedCRS");
     612           1 :             return FALSE;
     613             :         }
     614          54 :         oTMS.osSRS = pszSupportedCRS;
     615         108 :         if (oTMS.oSRS.SetFromUserInput(
     616         108 :                 FixCRSName(pszSupportedCRS),
     617          54 :                 OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) !=
     618             :             OGRERR_NONE)
     619             :         {
     620           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse CRS '%s'",
     621             :                      pszSupportedCRS);
     622           0 :             return FALSE;
     623             :         }
     624             :         const bool bSwap =
     625         154 :             !STARTS_WITH_CI(pszSupportedCRS, "EPSG:") &&
     626         100 :             (CPL_TO_BOOL(oTMS.oSRS.EPSGTreatsAsLatLong()) ||
     627          47 :              CPL_TO_BOOL(oTMS.oSRS.EPSGTreatsAsNorthingEasting()));
     628          54 :         CPLXMLNode *psBB = CPLGetXMLNode(psIter, "BoundingBox");
     629          54 :         oTMS.bBoundingBoxValid = false;
     630          54 :         if (psBB != nullptr)
     631             :         {
     632          18 :             CPLString osCRS = CPLGetXMLValue(psBB, "crs", "");
     633           9 :             if (EQUAL(osCRS, "") || EQUAL(osCRS, pszSupportedCRS))
     634             :             {
     635             :                 CPLString osLowerCorner =
     636          18 :                     CPLGetXMLValue(psBB, "LowerCorner", "");
     637             :                 CPLString osUpperCorner =
     638          18 :                     CPLGetXMLValue(psBB, "UpperCorner", "");
     639           9 :                 if (!osLowerCorner.empty() && !osUpperCorner.empty())
     640             :                 {
     641           9 :                     char **papszLC = CSLTokenizeString(osLowerCorner);
     642           9 :                     char **papszUC = CSLTokenizeString(osUpperCorner);
     643           9 :                     if (CSLCount(papszLC) == 2 && CSLCount(papszUC) == 2)
     644             :                     {
     645           9 :                         oTMS.sBoundingBox.MinX =
     646           9 :                             CPLAtof(papszLC[(bSwap) ? 1 : 0]);
     647           9 :                         oTMS.sBoundingBox.MinY =
     648           9 :                             CPLAtof(papszLC[(bSwap) ? 0 : 1]);
     649           9 :                         oTMS.sBoundingBox.MaxX =
     650           9 :                             CPLAtof(papszUC[(bSwap) ? 1 : 0]);
     651           9 :                         oTMS.sBoundingBox.MaxY =
     652           9 :                             CPLAtof(papszUC[(bSwap) ? 0 : 1]);
     653           9 :                         oTMS.bBoundingBoxValid = true;
     654             :                     }
     655           9 :                     CSLDestroy(papszLC);
     656           9 :                     CSLDestroy(papszUC);
     657             :                 }
     658             :             }
     659             :         }
     660             :         else
     661             :         {
     662             :             const char *pszWellKnownScaleSet =
     663          45 :                 CPLGetXMLValue(psIter, "WellKnownScaleSet", "");
     664          45 :             if (EQUAL(pszIdentifier, "GoogleCRS84Quad") ||
     665          45 :                 EQUAL(pszWellKnownScaleSet,
     666          45 :                       "urn:ogc:def:wkss:OGC:1.0:GoogleCRS84Quad") ||
     667          45 :                 EQUAL(pszIdentifier, "GlobalCRS84Scale") ||
     668          45 :                 EQUAL(pszWellKnownScaleSet,
     669             :                       "urn:ogc:def:wkss:OGC:1.0:GlobalCRS84Scale"))
     670             :             {
     671           0 :                 oTMS.sBoundingBox.MinX = -180;
     672           0 :                 oTMS.sBoundingBox.MinY = -90;
     673           0 :                 oTMS.sBoundingBox.MaxX = 180;
     674           0 :                 oTMS.sBoundingBox.MaxY = 90;
     675           0 :                 oTMS.bBoundingBoxValid = true;
     676             :             }
     677             :         }
     678             : 
     679          54 :         bool bFoundTileMatrix = false;
     680         308 :         for (CPLXMLNode *psSubIter = psIter->psChild; psSubIter != nullptr;
     681         254 :              psSubIter = psSubIter->psNext)
     682             :         {
     683         262 :             if (psSubIter->eType != CXT_Element ||
     684         262 :                 strcmp(psSubIter->pszValue, "TileMatrix") != 0)
     685         129 :                 continue;
     686             :             const char *l_pszIdentifier =
     687         133 :                 CPLGetXMLValue(psSubIter, "Identifier", nullptr);
     688             :             const char *pszScaleDenominator =
     689         133 :                 CPLGetXMLValue(psSubIter, "ScaleDenominator", nullptr);
     690             :             const char *pszTopLeftCorner =
     691         133 :                 CPLGetXMLValue(psSubIter, "TopLeftCorner", nullptr);
     692             :             const char *pszTileWidth =
     693         133 :                 CPLGetXMLValue(psSubIter, "TileWidth", nullptr);
     694             :             const char *pszTileHeight =
     695         133 :                 CPLGetXMLValue(psSubIter, "TileHeight", nullptr);
     696             :             const char *pszMatrixWidth =
     697         133 :                 CPLGetXMLValue(psSubIter, "MatrixWidth", nullptr);
     698             :             const char *pszMatrixHeight =
     699         133 :                 CPLGetXMLValue(psSubIter, "MatrixHeight", nullptr);
     700         133 :             if (l_pszIdentifier == nullptr || pszScaleDenominator == nullptr ||
     701         132 :                 pszTopLeftCorner == nullptr ||
     702         132 :                 strchr(pszTopLeftCorner, ' ') == nullptr ||
     703         132 :                 pszTileWidth == nullptr || pszTileHeight == nullptr ||
     704         132 :                 pszMatrixWidth == nullptr || pszMatrixHeight == nullptr)
     705             :             {
     706           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     707             :                          "Missing required element in TileMatrix element");
     708           1 :                 return FALSE;
     709             :             }
     710         132 :             WMTSTileMatrix oTM;
     711         132 :             oTM.osIdentifier = l_pszIdentifier;
     712         132 :             oTM.dfScaleDenominator = CPLAtof(pszScaleDenominator);
     713         132 :             oTM.dfPixelSize = oTM.dfScaleDenominator * WMTS_PITCH;
     714         132 :             if (oTM.dfPixelSize <= 0.0)
     715             :             {
     716           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     717             :                          "Invalid ScaleDenominator");
     718           0 :                 return FALSE;
     719             :             }
     720         132 :             if (oTMS.oSRS.IsGeographic())
     721          18 :                 oTM.dfPixelSize *= WMTS_WGS84_DEG_PER_METER;
     722         132 :             double dfVal1 = CPLAtof(pszTopLeftCorner);
     723         132 :             double dfVal2 = CPLAtof(strchr(pszTopLeftCorner, ' ') + 1);
     724         132 :             if (!bSwap)
     725             :             {
     726         113 :                 oTM.dfTLX = dfVal1;
     727         113 :                 oTM.dfTLY = dfVal2;
     728             :             }
     729             :             else
     730             :             {
     731          19 :                 oTM.dfTLX = dfVal2;
     732          19 :                 oTM.dfTLY = dfVal1;
     733             :             }
     734             : 
     735             :             // Hack for http://osm.geobretagne.fr/gwc01/service/wmts?request=getcapabilities
     736             :             // or https://trek.nasa.gov/tiles/Mars/EQ/THEMIS_NightIR_ControlledMosaics_100m_v2_oct2018/1.0.0/WMTSCapabilities.xml
     737         132 :             if (oTM.dfTLY == -180.0 &&
     738           0 :                 (STARTS_WITH_CI(l_pszIdentifier, "EPSG:4326:") ||
     739           0 :                  (oTMS.oSRS.IsGeographic() && oTM.dfTLX == 90)))
     740             :             {
     741           0 :                 if (!bHasWarnedAutoSwap)
     742             :                 {
     743           0 :                     bHasWarnedAutoSwap = true;
     744           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     745             :                              "Auto-correcting wrongly swapped "
     746             :                              "TileMatrix.TopLeftCorner coordinates. "
     747             :                              "They should be in latitude, longitude order "
     748             :                              "but are presented in longitude, latitude order. "
     749             :                              "This should be reported to the server "
     750             :                              "administrator.");
     751             :                 }
     752           0 :                 std::swap(oTM.dfTLX, oTM.dfTLY);
     753             :             }
     754             : 
     755             :             // Hack for "https://t0.tianditu.gov.cn/img_w/wmts?SERVICE=WMTS&REQUEST=GetCapabilities&VERSION=1.0.0&tk=ec899a50c7830ea2416ca182285236f3"
     756             :             // which returns swapped coordinates for WebMercator
     757         132 :             if (std::fabs(oTM.dfTLX - 20037508.3427892) < 1e-4 &&
     758           0 :                 std::fabs(oTM.dfTLY - (-20037508.3427892)) < 1e-4)
     759             :             {
     760           0 :                 if (!bHasWarnedAutoSwap)
     761             :                 {
     762           0 :                     bHasWarnedAutoSwap = true;
     763           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     764             :                              "Auto-correcting wrongly swapped "
     765             :                              "TileMatrix.TopLeftCorner coordinates. This "
     766             :                              "should be reported to the server administrator.");
     767             :                 }
     768           0 :                 std::swap(oTM.dfTLX, oTM.dfTLY);
     769             :             }
     770             : 
     771         132 :             oTM.nTileWidth = atoi(pszTileWidth);
     772         132 :             oTM.nTileHeight = atoi(pszTileHeight);
     773         132 :             if (oTM.nTileWidth <= 0 || oTM.nTileWidth > 4096 ||
     774         132 :                 oTM.nTileHeight <= 0 || oTM.nTileHeight > 4096)
     775             :             {
     776           0 :                 CPLError(CE_Failure, CPLE_AppDefined,
     777             :                          "Invalid TileWidth/TileHeight element");
     778           0 :                 return FALSE;
     779             :             }
     780         132 :             oTM.nMatrixWidth = atoi(pszMatrixWidth);
     781         132 :             oTM.nMatrixHeight = atoi(pszMatrixHeight);
     782             :             // http://datacarto.geonormandie.fr/mapcache/wmts?SERVICE=WMTS&REQUEST=GetCapabilities
     783             :             // has a TileMatrix 0 with MatrixWidth = MatrixHeight = 0
     784         132 :             if (oTM.nMatrixWidth < 1 || oTM.nMatrixHeight < 1)
     785           0 :                 continue;
     786         132 :             oTMS.aoTM.push_back(std::move(oTM));
     787         140 :             if ((nMaxZoomLevel >= 0 &&
     788         260 :                  static_cast<int>(oTMS.aoTM.size()) - 1 == nMaxZoomLevel) ||
     789         128 :                 (!osMaxTileMatrixIdentifier.empty() &&
     790           7 :                  EQUAL(osMaxTileMatrixIdentifier, l_pszIdentifier)))
     791             :             {
     792           7 :                 bFoundTileMatrix = true;
     793           7 :                 break;
     794             :             }
     795             :         }
     796          53 :         if (nMaxZoomLevel >= 0 && !bFoundTileMatrix)
     797             :         {
     798           2 :             CPLError(
     799             :                 CE_Failure, CPLE_AppDefined,
     800             :                 "Cannot find TileMatrix of zoom level %d in TileMatrixSet '%s'",
     801             :                 nMaxZoomLevel, osIdentifier.c_str());
     802           2 :             return FALSE;
     803             :         }
     804          51 :         if (!osMaxTileMatrixIdentifier.empty() && !bFoundTileMatrix)
     805             :         {
     806           2 :             CPLError(CE_Failure, CPLE_AppDefined,
     807             :                      "Cannot find TileMatrix '%s' in TileMatrixSet '%s'",
     808             :                      osMaxTileMatrixIdentifier.c_str(), osIdentifier.c_str());
     809           2 :             return FALSE;
     810             :         }
     811          49 :         if (oTMS.aoTM.empty())
     812             :         {
     813           1 :             CPLError(CE_Failure, CPLE_AppDefined,
     814             :                      "Cannot find TileMatrix in TileMatrixSet '%s'",
     815             :                      osIdentifier.c_str());
     816           1 :             return FALSE;
     817             :         }
     818          48 :         return TRUE;
     819             :     }
     820           0 :     CPLError(CE_Failure, CPLE_AppDefined, "Cannot find TileMatrixSet '%s'",
     821             :              osIdentifier.c_str());
     822           0 :     return FALSE;
     823             : }
     824             : 
     825             : /************************************************************************/
     826             : /*                              ReadTMLimits()                          */
     827             : /************************************************************************/
     828             : 
     829           2 : int WMTSDataset::ReadTMLimits(
     830             :     CPLXMLNode *psTMSLimits,
     831             :     std::map<CPLString, WMTSTileMatrixLimits> &aoMapTileMatrixLimits)
     832             : {
     833           4 :     for (CPLXMLNode *psIter = psTMSLimits->psChild; psIter;
     834           2 :          psIter = psIter->psNext)
     835             :     {
     836           2 :         if (psIter->eType != CXT_Element ||
     837           2 :             strcmp(psIter->pszValue, "TileMatrixLimits") != 0)
     838           0 :             continue;
     839           2 :         WMTSTileMatrixLimits oTMLimits;
     840             :         const char *pszTileMatrix =
     841           2 :             CPLGetXMLValue(psIter, "TileMatrix", nullptr);
     842             :         const char *pszMinTileRow =
     843           2 :             CPLGetXMLValue(psIter, "MinTileRow", nullptr);
     844             :         const char *pszMaxTileRow =
     845           2 :             CPLGetXMLValue(psIter, "MaxTileRow", nullptr);
     846             :         const char *pszMinTileCol =
     847           2 :             CPLGetXMLValue(psIter, "MinTileCol", nullptr);
     848             :         const char *pszMaxTileCol =
     849           2 :             CPLGetXMLValue(psIter, "MaxTileCol", nullptr);
     850           2 :         if (pszTileMatrix == nullptr || pszMinTileRow == nullptr ||
     851           2 :             pszMaxTileRow == nullptr || pszMinTileCol == nullptr ||
     852             :             pszMaxTileCol == nullptr)
     853             :         {
     854           0 :             CPLError(CE_Failure, CPLE_AppDefined,
     855             :                      "Missing required element in TileMatrixLimits element");
     856           0 :             return FALSE;
     857             :         }
     858           2 :         oTMLimits.osIdentifier = pszTileMatrix;
     859           2 :         oTMLimits.nMinTileRow = atoi(pszMinTileRow);
     860           2 :         oTMLimits.nMaxTileRow = atoi(pszMaxTileRow);
     861           2 :         oTMLimits.nMinTileCol = atoi(pszMinTileCol);
     862           2 :         oTMLimits.nMaxTileCol = atoi(pszMaxTileCol);
     863           2 :         aoMapTileMatrixLimits[pszTileMatrix] = std::move(oTMLimits);
     864             :     }
     865           2 :     return TRUE;
     866             : }
     867             : 
     868             : /************************************************************************/
     869             : /*                               Replace()                              */
     870             : /************************************************************************/
     871             : 
     872         380 : CPLString WMTSDataset::Replace(const CPLString &osStr, const char *pszOld,
     873             :                                const char *pszNew)
     874             : {
     875         380 :     size_t nPos = osStr.ifind(pszOld);
     876         380 :     if (nPos == std::string::npos)
     877          59 :         return osStr;
     878         642 :     CPLString osRet(osStr.substr(0, nPos));
     879         321 :     osRet += pszNew;
     880         321 :     osRet += osStr.substr(nPos + strlen(pszOld));
     881         321 :     return osRet;
     882             : }
     883             : 
     884             : /************************************************************************/
     885             : /*                       GetCapabilitiesResponse()                      */
     886             : /************************************************************************/
     887             : 
     888          72 : CPLXMLNode *WMTSDataset::GetCapabilitiesResponse(const CPLString &osFilename,
     889             :                                                  CSLConstList papszHTTPOptions)
     890             : {
     891             :     CPLXMLNode *psXML;
     892             :     VSIStatBufL sStat;
     893          72 :     if (VSIStatL(osFilename, &sStat) == 0)
     894          66 :         psXML = CPLParseXMLFile(osFilename);
     895             :     else
     896             :     {
     897           6 :         CPLHTTPResult *psResult = CPLHTTPFetch(osFilename, papszHTTPOptions);
     898           6 :         if (psResult == nullptr)
     899           0 :             return nullptr;
     900           6 :         if (psResult->pabyData == nullptr)
     901             :         {
     902           3 :             CPLHTTPDestroyResult(psResult);
     903           3 :             return nullptr;
     904             :         }
     905           6 :         psXML = CPLParseXMLString(
     906           3 :             reinterpret_cast<const char *>(psResult->pabyData));
     907           3 :         CPLHTTPDestroyResult(psResult);
     908             :     }
     909          69 :     return psXML;
     910             : }
     911             : 
     912             : /************************************************************************/
     913             : /*                          WMTSAddOtherXML()                           */
     914             : /************************************************************************/
     915             : 
     916         165 : static void WMTSAddOtherXML(CPLXMLNode *psRoot, const char *pszElement,
     917             :                             CPLString &osOtherXML)
     918             : {
     919         165 :     CPLXMLNode *psElement = CPLGetXMLNode(psRoot, pszElement);
     920         165 :     if (psElement)
     921             :     {
     922          60 :         CPLXMLNode *psNext = psElement->psNext;
     923          60 :         psElement->psNext = nullptr;
     924          60 :         char *pszTmp = CPLSerializeXMLTree(psElement);
     925          60 :         osOtherXML += pszTmp;
     926          60 :         CPLFree(pszTmp);
     927          60 :         psElement->psNext = psNext;
     928             :     }
     929         165 : }
     930             : 
     931             : /************************************************************************/
     932             : /*                          GetOperationKVPURL()                        */
     933             : /************************************************************************/
     934             : 
     935          68 : CPLString WMTSDataset::GetOperationKVPURL(CPLXMLNode *psXML,
     936             :                                           const char *pszOperation)
     937             : {
     938          68 :     CPLString osRet;
     939          68 :     CPLXMLNode *psOM = CPLGetXMLNode(psXML, "=Capabilities.OperationsMetadata");
     940         106 :     for (CPLXMLNode *psIter = psOM ? psOM->psChild : nullptr; psIter != nullptr;
     941          38 :          psIter = psIter->psNext)
     942             :     {
     943         139 :         if (psIter->eType != CXT_Element ||
     944          76 :             strcmp(psIter->pszValue, "Operation") != 0 ||
     945          38 :             !EQUAL(CPLGetXMLValue(psIter, "name", ""), pszOperation))
     946             :         {
     947          25 :             continue;
     948             :         }
     949          13 :         CPLXMLNode *psHTTP = CPLGetXMLNode(psIter, "DCP.HTTP");
     950          13 :         for (CPLXMLNode *psGet = psHTTP ? psHTTP->psChild : nullptr;
     951          26 :              psGet != nullptr; psGet = psGet->psNext)
     952             :         {
     953          13 :             if (psGet->eType != CXT_Element ||
     954          13 :                 strcmp(psGet->pszValue, "Get") != 0)
     955             :             {
     956           0 :                 continue;
     957             :             }
     958          13 :             if (!EQUAL(CPLGetXMLValue(psGet, "Constraint.AllowedValues.Value",
     959             :                                       "KVP"),
     960             :                        "KVP"))
     961           1 :                 continue;
     962          12 :             osRet = CPLGetXMLValue(psGet, "href", "");
     963             :         }
     964             :     }
     965          68 :     return osRet;
     966             : }
     967             : 
     968             : /************************************************************************/
     969             : /*                           BuildHTTPRequestOpts()                     */
     970             : /************************************************************************/
     971             : 
     972         127 : CPLStringList WMTSDataset::BuildHTTPRequestOpts(CPLString osOtherXML)
     973             : {
     974         127 :     osOtherXML = "<Root>" + osOtherXML + "</Root>";
     975         127 :     CPLXMLNode *psXML = CPLParseXMLString(osOtherXML);
     976         127 :     CPLStringList opts;
     977         635 :     for (const char *pszOptionName :
     978         762 :          {"Timeout", "UserAgent", "Accept", "Referer", "UserPwd"})
     979             :     {
     980         635 :         if (const char *pszVal = CPLGetXMLValue(psXML, pszOptionName, nullptr))
     981             :         {
     982           8 :             opts.SetNameValue(CPLString(pszOptionName).toupper(), pszVal);
     983             :         }
     984             :     }
     985         127 :     if (CPLTestBool(CPLGetXMLValue(psXML, "UnsafeSSL", "false")))
     986             :     {
     987         125 :         opts.SetNameValue("UNSAFESSL", "1");
     988             :     }
     989         127 :     CPLDestroyXMLNode(psXML);
     990         127 :     return opts;
     991             : }
     992             : 
     993             : /************************************************************************/
     994             : /*                                Open()                                */
     995             : /************************************************************************/
     996             : 
     997          71 : GDALDataset *WMTSDataset::Open(GDALOpenInfo *poOpenInfo)
     998             : {
     999          71 :     if (!WMTSDriverIdentify(poOpenInfo))
    1000           0 :         return nullptr;
    1001             : 
    1002          71 :     CPLXMLNode *psXML = nullptr;
    1003         142 :     CPLString osTileFormat;
    1004         142 :     CPLString osInfoFormat;
    1005             : 
    1006             :     CPLString osGetCapabilitiesURL =
    1007         142 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "URL", "");
    1008             :     CPLString osLayer =
    1009         142 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "LAYER", "");
    1010             :     CPLString osTMS =
    1011         142 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "TILEMATRIXSET", "");
    1012             :     CPLString osMaxTileMatrixIdentifier =
    1013         142 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "TILEMATRIX", "");
    1014         213 :     int nUserMaxZoomLevel = atoi(CSLFetchNameValueDef(
    1015          71 :         poOpenInfo->papszOpenOptions, "ZOOM_LEVEL",
    1016          71 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "ZOOMLEVEL", "-1")));
    1017             :     CPLString osStyle =
    1018         142 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "STYLE", "");
    1019             : 
    1020          71 :     int bExtendBeyondDateLine = CPLFetchBool(poOpenInfo->papszOpenOptions,
    1021          71 :                                              "EXTENDBEYONDDATELINE", false);
    1022             : 
    1023             :     CPLString osOtherXML =
    1024             :         "<Cache />"
    1025             :         "<UnsafeSSL>true</UnsafeSSL>"
    1026             :         "<ZeroBlockHttpCodes>204,404</ZeroBlockHttpCodes>"
    1027         142 :         "<ZeroBlockOnServerException>true</ZeroBlockOnServerException>";
    1028             : 
    1029          71 :     if (STARTS_WITH_CI(poOpenInfo->pszFilename, "WMTS:"))
    1030             :     {
    1031          56 :         char **papszTokens = CSLTokenizeString2(poOpenInfo->pszFilename + 5,
    1032             :                                                 ",", CSLT_HONOURSTRINGS);
    1033          56 :         if (papszTokens && papszTokens[0])
    1034             :         {
    1035          50 :             osGetCapabilitiesURL = papszTokens[0];
    1036          67 :             for (char **papszIter = papszTokens + 1; *papszIter; papszIter++)
    1037             :             {
    1038          17 :                 char *pszKey = nullptr;
    1039          17 :                 const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
    1040          17 :                 if (pszKey && pszValue)
    1041             :                 {
    1042          17 :                     if (EQUAL(pszKey, "layer"))
    1043           3 :                         osLayer = pszValue;
    1044          14 :                     else if (EQUAL(pszKey, "tilematrixset"))
    1045           4 :                         osTMS = pszValue;
    1046          10 :                     else if (EQUAL(pszKey, "tilematrix"))
    1047           3 :                         osMaxTileMatrixIdentifier = pszValue;
    1048           7 :                     else if (EQUAL(pszKey, "zoom_level") ||
    1049           4 :                              EQUAL(pszKey, "zoomlevel"))
    1050           3 :                         nUserMaxZoomLevel = atoi(pszValue);
    1051           4 :                     else if (EQUAL(pszKey, "style"))
    1052           3 :                         osStyle = pszValue;
    1053           1 :                     else if (EQUAL(pszKey, "extendbeyonddateline"))
    1054           1 :                         bExtendBeyondDateLine = CPLTestBool(pszValue);
    1055             :                     else
    1056           0 :                         CPLError(CE_Warning, CPLE_AppDefined,
    1057             :                                  "Unknown parameter: %s'", pszKey);
    1058             :                 }
    1059          17 :                 CPLFree(pszKey);
    1060             :             }
    1061             :         }
    1062          56 :         CSLDestroy(papszTokens);
    1063             : 
    1064          56 :         const CPLStringList aosHTTPOptions(BuildHTTPRequestOpts(osOtherXML));
    1065          56 :         psXML = GetCapabilitiesResponse(osGetCapabilitiesURL,
    1066             :                                         aosHTTPOptions.List());
    1067             :     }
    1068          17 :     else if (poOpenInfo->IsSingleAllowedDriver("WMTS") &&
    1069           2 :              (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
    1070           1 :               STARTS_WITH(poOpenInfo->pszFilename, "https://")))
    1071             :     {
    1072           1 :         const CPLStringList aosHTTPOptions(BuildHTTPRequestOpts(osOtherXML));
    1073           1 :         psXML = GetCapabilitiesResponse(poOpenInfo->pszFilename,
    1074             :                                         aosHTTPOptions.List());
    1075             :     }
    1076             : 
    1077          71 :     int bHasAOI = FALSE;
    1078          71 :     OGREnvelope sAOI;
    1079          71 :     int nBands = 4;
    1080          71 :     GDALDataType eDataType = GDT_Byte;
    1081         142 :     CPLString osProjection;
    1082         142 :     CPLString osExtraQueryParameters;
    1083             : 
    1084          53 :     if ((psXML != nullptr && CPLGetXMLNode(psXML, "=GDAL_WMTS") != nullptr) ||
    1085         183 :         STARTS_WITH_CI(poOpenInfo->pszFilename, "<GDAL_WMTS") ||
    1086          59 :         (poOpenInfo->nHeaderBytes > 0 &&
    1087          10 :          strstr(reinterpret_cast<const char *>(poOpenInfo->pabyHeader),
    1088             :                 "<GDAL_WMTS")))
    1089             :     {
    1090             :         CPLXMLNode *psGDALWMTS;
    1091          18 :         if (psXML != nullptr && CPLGetXMLNode(psXML, "=GDAL_WMTS") != nullptr)
    1092           8 :             psGDALWMTS = CPLCloneXMLTree(psXML);
    1093          10 :         else if (STARTS_WITH_CI(poOpenInfo->pszFilename, "<GDAL_WMTS"))
    1094           4 :             psGDALWMTS = CPLParseXMLString(poOpenInfo->pszFilename);
    1095             :         else
    1096           6 :             psGDALWMTS = CPLParseXMLFile(poOpenInfo->pszFilename);
    1097          18 :         if (psGDALWMTS == nullptr)
    1098           3 :             return nullptr;
    1099          17 :         CPLXMLNode *psRoot = CPLGetXMLNode(psGDALWMTS, "=GDAL_WMTS");
    1100          17 :         if (psRoot == nullptr)
    1101             :         {
    1102           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1103             :                      "Cannot find root <GDAL_WMTS>");
    1104           1 :             CPLDestroyXMLNode(psGDALWMTS);
    1105           1 :             return nullptr;
    1106             :         }
    1107          16 :         osGetCapabilitiesURL = CPLGetXMLValue(psRoot, "GetCapabilitiesUrl", "");
    1108          16 :         if (osGetCapabilitiesURL.empty())
    1109             :         {
    1110           1 :             CPLError(CE_Failure, CPLE_AppDefined,
    1111             :                      "Missing <GetCapabilitiesUrl>");
    1112           1 :             CPLDestroyXMLNode(psGDALWMTS);
    1113           1 :             return nullptr;
    1114             :         }
    1115             :         osExtraQueryParameters =
    1116          15 :             CPLGetXMLValue(psRoot, "ExtraQueryParameters", "");
    1117          15 :         if (!osExtraQueryParameters.empty() && osExtraQueryParameters[0] != '&')
    1118           0 :             osExtraQueryParameters = '&' + osExtraQueryParameters;
    1119             : 
    1120          15 :         osGetCapabilitiesURL += osExtraQueryParameters;
    1121             : 
    1122          15 :         osLayer = CPLGetXMLValue(psRoot, "Layer", osLayer);
    1123          15 :         osTMS = CPLGetXMLValue(psRoot, "TileMatrixSet", osTMS);
    1124             :         osMaxTileMatrixIdentifier =
    1125          15 :             CPLGetXMLValue(psRoot, "TileMatrix", osMaxTileMatrixIdentifier);
    1126          15 :         nUserMaxZoomLevel = atoi(CPLGetXMLValue(
    1127             :             psRoot, "ZoomLevel", CPLSPrintf("%d", nUserMaxZoomLevel)));
    1128          15 :         osStyle = CPLGetXMLValue(psRoot, "Style", osStyle);
    1129          15 :         osTileFormat = CPLGetXMLValue(psRoot, "Format", osTileFormat);
    1130          15 :         osInfoFormat = CPLGetXMLValue(psRoot, "InfoFormat", osInfoFormat);
    1131          15 :         osProjection = CPLGetXMLValue(psRoot, "Projection", osProjection);
    1132          15 :         bExtendBeyondDateLine = CPLTestBool(
    1133             :             CPLGetXMLValue(psRoot, "ExtendBeyondDateLine",
    1134             :                            (bExtendBeyondDateLine) ? "true" : "false"));
    1135             : 
    1136          15 :         osOtherXML = "";
    1137         165 :         for (const char *pszXMLElement :
    1138             :              {"Cache", "MaxConnections", "Timeout", "OfflineMode", "UserAgent",
    1139             :               "Accept", "UserPwd", "UnsafeSSL", "Referer", "ZeroBlockHttpCodes",
    1140         180 :               "ZeroBlockOnServerException"})
    1141             :         {
    1142         165 :             WMTSAddOtherXML(psRoot, pszXMLElement, osOtherXML);
    1143             :         }
    1144             : 
    1145          15 :         nBands = atoi(CPLGetXMLValue(psRoot, "BandsCount", "4"));
    1146          15 :         const char *pszDataType = CPLGetXMLValue(psRoot, "DataType", "Byte");
    1147          15 :         eDataType = GDALGetDataTypeByName(pszDataType);
    1148          15 :         if ((eDataType == GDT_Unknown) || (eDataType >= GDT_TypeCount))
    1149             :         {
    1150           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1151             :                      "GDALWMTS: Invalid value in DataType. Data type \"%s\" is "
    1152             :                      "not supported.",
    1153             :                      pszDataType);
    1154           0 :             CPLDestroyXMLNode(psGDALWMTS);
    1155           0 :             return nullptr;
    1156             :         }
    1157             : 
    1158             :         const char *pszULX =
    1159          15 :             CPLGetXMLValue(psRoot, "DataWindow.UpperLeftX", nullptr);
    1160             :         const char *pszULY =
    1161          15 :             CPLGetXMLValue(psRoot, "DataWindow.UpperLeftY", nullptr);
    1162             :         const char *pszLRX =
    1163          15 :             CPLGetXMLValue(psRoot, "DataWindow.LowerRightX", nullptr);
    1164             :         const char *pszLRY =
    1165          15 :             CPLGetXMLValue(psRoot, "DataWindow.LowerRightY", nullptr);
    1166          15 :         if (pszULX && pszULY && pszLRX && pszLRY)
    1167             :         {
    1168          14 :             sAOI.MinX = CPLAtof(pszULX);
    1169          14 :             sAOI.MaxY = CPLAtof(pszULY);
    1170          14 :             sAOI.MaxX = CPLAtof(pszLRX);
    1171          14 :             sAOI.MinY = CPLAtof(pszLRY);
    1172          14 :             bHasAOI = TRUE;
    1173             :         }
    1174             : 
    1175          15 :         CPLDestroyXMLNode(psGDALWMTS);
    1176             : 
    1177          15 :         CPLDestroyXMLNode(psXML);
    1178          15 :         const CPLStringList aosHTTPOptions(BuildHTTPRequestOpts(osOtherXML));
    1179          15 :         psXML = GetCapabilitiesResponse(osGetCapabilitiesURL,
    1180             :                                         aosHTTPOptions.List());
    1181             :     }
    1182          53 :     else if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "WMTS:") &&
    1183           5 :              !STARTS_WITH(poOpenInfo->pszFilename, "http://") &&
    1184           4 :              !STARTS_WITH(poOpenInfo->pszFilename, "https://"))
    1185             :     {
    1186           4 :         osGetCapabilitiesURL = poOpenInfo->pszFilename;
    1187           4 :         psXML = CPLParseXMLFile(poOpenInfo->pszFilename);
    1188             :     }
    1189          68 :     if (psXML == nullptr)
    1190           4 :         return nullptr;
    1191          64 :     CPLStripXMLNamespace(psXML, nullptr, TRUE);
    1192             : 
    1193          64 :     CPLXMLNode *psContents = CPLGetXMLNode(psXML, "=Capabilities.Contents");
    1194          64 :     if (psContents == nullptr)
    1195             :     {
    1196           1 :         CPLError(CE_Failure, CPLE_AppDefined,
    1197             :                  "Missing Capabilities.Contents element");
    1198           1 :         CPLDestroyXMLNode(psXML);
    1199           1 :         return nullptr;
    1200             :     }
    1201             : 
    1202          63 :     if (STARTS_WITH(osGetCapabilitiesURL, "/vsimem/"))
    1203             :     {
    1204          59 :         osGetCapabilitiesURL = GetOperationKVPURL(psXML, "GetCapabilities");
    1205          59 :         if (osGetCapabilitiesURL.empty())
    1206             :         {
    1207             :             // (ERO) I'm not even sure this is correct at all...
    1208          55 :             const char *pszHref = CPLGetXMLValue(
    1209             :                 psXML, "=Capabilities.ServiceMetadataURL.href", nullptr);
    1210          55 :             if (pszHref)
    1211          26 :                 osGetCapabilitiesURL = pszHref;
    1212             :         }
    1213             :         else
    1214             :         {
    1215             :             osGetCapabilitiesURL =
    1216           4 :                 CPLURLAddKVP(osGetCapabilitiesURL, "service", "WMTS");
    1217           8 :             osGetCapabilitiesURL = CPLURLAddKVP(osGetCapabilitiesURL, "request",
    1218           4 :                                                 "GetCapabilities");
    1219             :         }
    1220             :     }
    1221         126 :     CPLString osCapabilitiesFilename(osGetCapabilitiesURL);
    1222          63 :     if (!STARTS_WITH_CI(osCapabilitiesFilename, "WMTS:"))
    1223          63 :         osCapabilitiesFilename = "WMTS:" + osGetCapabilitiesURL;
    1224             : 
    1225          63 :     int nLayerCount = 0;
    1226         126 :     CPLStringList aosSubDatasets;
    1227         126 :     CPLString osSelectLayer(osLayer), osSelectTMS(osTMS),
    1228         126 :         osSelectStyle(osStyle);
    1229         126 :     CPLString osSelectLayerTitle, osSelectLayerAbstract;
    1230         126 :     CPLString osSelectTileFormat(osTileFormat),
    1231         126 :         osSelectInfoFormat(osInfoFormat);
    1232          63 :     int nCountTileFormat = 0;
    1233          63 :     int nCountInfoFormat = 0;
    1234         126 :     CPLString osURLTileTemplate;
    1235         126 :     CPLString osURLFeatureInfoTemplate;
    1236         126 :     std::set<CPLString> aoSetLayers;
    1237         126 :     std::map<CPLString, OGREnvelope> aoMapBoundingBox;
    1238         126 :     std::map<CPLString, WMTSTileMatrixLimits> aoMapTileMatrixLimits;
    1239         126 :     std::map<CPLString, CPLString> aoMapDimensions;
    1240          63 :     bool bHasWarnedAutoSwap = false;
    1241          63 :     bool bHasWarnedAutoSwapBoundingBox = false;
    1242             : 
    1243             :     // Collect TileMatrixSet identifiers
    1244         126 :     std::set<std::string> oSetTMSIdentifiers;
    1245         202 :     for (CPLXMLNode *psIter = psContents->psChild; psIter != nullptr;
    1246         139 :          psIter = psIter->psNext)
    1247             :     {
    1248         139 :         if (psIter->eType != CXT_Element ||
    1249         139 :             strcmp(psIter->pszValue, "TileMatrixSet") != 0)
    1250          62 :             continue;
    1251             :         const char *pszIdentifier =
    1252          77 :             CPLGetXMLValue(psIter, "Identifier", nullptr);
    1253          77 :         if (pszIdentifier)
    1254          77 :             oSetTMSIdentifiers.insert(pszIdentifier);
    1255             :     }
    1256             : 
    1257         202 :     for (CPLXMLNode *psIter = psContents->psChild; psIter != nullptr;
    1258         139 :          psIter = psIter->psNext)
    1259             :     {
    1260         139 :         if (psIter->eType != CXT_Element ||
    1261         139 :             strcmp(psIter->pszValue, "Layer") != 0)
    1262          78 :             continue;
    1263          62 :         const char *pszIdentifier = CPLGetXMLValue(psIter, "Identifier", "");
    1264          62 :         if (aoSetLayers.find(pszIdentifier) != aoSetLayers.end())
    1265             :         {
    1266           0 :             CPLError(CE_Warning, CPLE_AppDefined,
    1267             :                      "Several layers with identifier '%s'. Only first one kept",
    1268             :                      pszIdentifier);
    1269             :         }
    1270          62 :         aoSetLayers.insert(pszIdentifier);
    1271          62 :         if (!osLayer.empty() && strcmp(osLayer, pszIdentifier) != 0)
    1272           1 :             continue;
    1273          61 :         const char *pszTitle = CPLGetXMLValue(psIter, "Title", nullptr);
    1274          61 :         if (osSelectLayer.empty())
    1275             :         {
    1276          47 :             osSelectLayer = pszIdentifier;
    1277             :         }
    1278          61 :         if (strcmp(osSelectLayer, pszIdentifier) == 0)
    1279             :         {
    1280          61 :             if (pszTitle != nullptr)
    1281          36 :                 osSelectLayerTitle = pszTitle;
    1282             :             const char *pszAbstract =
    1283          61 :                 CPLGetXMLValue(psIter, "Abstract", nullptr);
    1284          61 :             if (pszAbstract != nullptr)
    1285          17 :                 osSelectLayerAbstract = pszAbstract;
    1286             :         }
    1287             : 
    1288         122 :         std::vector<CPLString> aosTMS;
    1289         122 :         std::vector<CPLString> aosStylesIdentifier;
    1290         122 :         std::vector<CPLString> aosStylesTitle;
    1291             : 
    1292          61 :         CPLXMLNode *psSubIter = psIter->psChild;
    1293         483 :         for (; psSubIter != nullptr; psSubIter = psSubIter->psNext)
    1294             :         {
    1295         422 :             if (psSubIter->eType != CXT_Element)
    1296           1 :                 continue;
    1297         842 :             if (strcmp(osSelectLayer, pszIdentifier) == 0 &&
    1298         421 :                 strcmp(psSubIter->pszValue, "Format") == 0)
    1299             :             {
    1300          15 :                 const char *pszValue = CPLGetXMLValue(psSubIter, "", "");
    1301          21 :                 if (!osTileFormat.empty() &&
    1302           6 :                     strcmp(osTileFormat, pszValue) != 0)
    1303           3 :                     continue;
    1304          12 :                 nCountTileFormat++;
    1305          12 :                 if (osSelectTileFormat.empty() || EQUAL(pszValue, "image/png"))
    1306             :                 {
    1307          12 :                     osSelectTileFormat = pszValue;
    1308             :                 }
    1309             :             }
    1310         812 :             else if (strcmp(osSelectLayer, pszIdentifier) == 0 &&
    1311         406 :                      strcmp(psSubIter->pszValue, "InfoFormat") == 0)
    1312             :             {
    1313           4 :                 const char *pszValue = CPLGetXMLValue(psSubIter, "", "");
    1314           4 :                 if (!osInfoFormat.empty() &&
    1315           0 :                     strcmp(osInfoFormat, pszValue) != 0)
    1316           0 :                     continue;
    1317           4 :                 nCountInfoFormat++;
    1318           4 :                 if (osSelectInfoFormat.empty() ||
    1319           0 :                     (EQUAL(pszValue, "application/vnd.ogc.gml") &&
    1320           0 :                      !EQUAL(osSelectInfoFormat,
    1321           4 :                             "application/vnd.ogc.gml/3.1.1")) ||
    1322           0 :                     EQUAL(pszValue, "application/vnd.ogc.gml/3.1.1"))
    1323             :                 {
    1324           4 :                     osSelectInfoFormat = pszValue;
    1325             :                 }
    1326             :             }
    1327         804 :             else if (strcmp(osSelectLayer, pszIdentifier) == 0 &&
    1328         402 :                      strcmp(psSubIter->pszValue, "Dimension") == 0)
    1329             :             {
    1330             :                 /* Cf http://wmts.geo.admin.ch/1.0.0/WMTSCapabilities.xml */
    1331             :                 const char *pszDimensionIdentifier =
    1332          21 :                     CPLGetXMLValue(psSubIter, "Identifier", nullptr);
    1333             :                 const char *pszDefault =
    1334          21 :                     CPLGetXMLValue(psSubIter, "Default", "");
    1335          21 :                 if (pszDimensionIdentifier != nullptr)
    1336          21 :                     aoMapDimensions[pszDimensionIdentifier] = pszDefault;
    1337             :             }
    1338         381 :             else if (strcmp(psSubIter->pszValue, "TileMatrixSetLink") == 0)
    1339             :             {
    1340             :                 const char *pszTMS =
    1341          77 :                     CPLGetXMLValue(psSubIter, "TileMatrixSet", "");
    1342          77 :                 if (oSetTMSIdentifiers.find(pszTMS) == oSetTMSIdentifiers.end())
    1343             :                 {
    1344           2 :                     CPLDebug("WMTS",
    1345             :                              "Layer %s has a TileMatrixSetLink to %s, "
    1346             :                              "but it is not defined as a TileMatrixSet",
    1347             :                              pszIdentifier, pszTMS);
    1348           2 :                     continue;
    1349             :                 }
    1350          75 :                 if (!osTMS.empty() && strcmp(osTMS, pszTMS) != 0)
    1351          13 :                     continue;
    1352         124 :                 if (strcmp(osSelectLayer, pszIdentifier) == 0 &&
    1353          62 :                     osSelectTMS.empty())
    1354             :                 {
    1355          40 :                     osSelectTMS = pszTMS;
    1356             :                 }
    1357         124 :                 if (strcmp(osSelectLayer, pszIdentifier) == 0 &&
    1358          62 :                     strcmp(osSelectTMS, pszTMS) == 0)
    1359             :                 {
    1360             :                     CPLXMLNode *psTMSLimits =
    1361          56 :                         CPLGetXMLNode(psSubIter, "TileMatrixSetLimits");
    1362          56 :                     if (psTMSLimits)
    1363           2 :                         ReadTMLimits(psTMSLimits, aoMapTileMatrixLimits);
    1364             :                 }
    1365          62 :                 aosTMS.push_back(pszTMS);
    1366             :             }
    1367         304 :             else if (strcmp(psSubIter->pszValue, "Style") == 0)
    1368             :             {
    1369          77 :                 int bIsDefault = CPLTestBool(
    1370          77 :                     CPLGetXMLValue(psSubIter, "isDefault", "false"));
    1371             :                 const char *l_pszIdentifier =
    1372          77 :                     CPLGetXMLValue(psSubIter, "Identifier", "");
    1373          77 :                 if (!osStyle.empty() && strcmp(osStyle, l_pszIdentifier) != 0)
    1374          15 :                     continue;
    1375             :                 const char *pszStyleTitle =
    1376          62 :                     CPLGetXMLValue(psSubIter, "Title", l_pszIdentifier);
    1377          62 :                 if (bIsDefault)
    1378             :                 {
    1379          32 :                     aosStylesIdentifier.insert(aosStylesIdentifier.begin(),
    1380          64 :                                                CPLString(l_pszIdentifier));
    1381          32 :                     aosStylesTitle.insert(aosStylesTitle.begin(),
    1382          64 :                                           CPLString(pszStyleTitle));
    1383          32 :                     if (strcmp(osSelectLayer, l_pszIdentifier) == 0 &&
    1384           0 :                         osSelectStyle.empty())
    1385             :                     {
    1386           0 :                         osSelectStyle = l_pszIdentifier;
    1387             :                     }
    1388             :                 }
    1389             :                 else
    1390             :                 {
    1391          30 :                     aosStylesIdentifier.push_back(l_pszIdentifier);
    1392          30 :                     aosStylesTitle.push_back(pszStyleTitle);
    1393             :                 }
    1394             :             }
    1395         454 :             else if (strcmp(osSelectLayer, pszIdentifier) == 0 &&
    1396         227 :                      (strcmp(psSubIter->pszValue, "BoundingBox") == 0 ||
    1397         219 :                       strcmp(psSubIter->pszValue, "WGS84BoundingBox") == 0))
    1398             :             {
    1399          70 :                 CPLString osCRS = CPLGetXMLValue(psSubIter, "crs", "");
    1400          35 :                 if (osCRS.empty())
    1401             :                 {
    1402          22 :                     if (strcmp(psSubIter->pszValue, "WGS84BoundingBox") == 0)
    1403             :                     {
    1404          22 :                         osCRS = "EPSG:4326";
    1405             :                     }
    1406             :                     else
    1407             :                     {
    1408           0 :                         int nCountTileMatrixSet = 0;
    1409           0 :                         CPLString osSingleTileMatrixSet;
    1410           0 :                         for (CPLXMLNode *psIter3 = psContents->psChild;
    1411           0 :                              psIter3 != nullptr; psIter3 = psIter3->psNext)
    1412             :                         {
    1413           0 :                             if (psIter3->eType != CXT_Element ||
    1414           0 :                                 strcmp(psIter3->pszValue, "TileMatrixSet") != 0)
    1415           0 :                                 continue;
    1416           0 :                             nCountTileMatrixSet++;
    1417           0 :                             if (nCountTileMatrixSet == 1)
    1418             :                                 osSingleTileMatrixSet =
    1419           0 :                                     CPLGetXMLValue(psIter3, "Identifier", "");
    1420             :                         }
    1421           0 :                         if (nCountTileMatrixSet == 1)
    1422             :                         {
    1423             :                             // For
    1424             :                             // 13-082_WMTS_Simple_Profile/schemas/wmts/1.0/profiles/WMTSSimple/examples/wmtsGetCapabilities_response_OSM.xml
    1425           0 :                             WMTSTileMatrixSet oTMS;
    1426           0 :                             if (ReadTMS(psContents, osSingleTileMatrixSet,
    1427           0 :                                         CPLString(), -1, oTMS,
    1428           0 :                                         bHasWarnedAutoSwap))
    1429             :                             {
    1430           0 :                                 osCRS = oTMS.osSRS;
    1431             :                             }
    1432             :                         }
    1433             :                     }
    1434             :                 }
    1435             :                 CPLString osLowerCorner =
    1436          70 :                     CPLGetXMLValue(psSubIter, "LowerCorner", "");
    1437             :                 CPLString osUpperCorner =
    1438          70 :                     CPLGetXMLValue(psSubIter, "UpperCorner", "");
    1439          70 :                 OGRSpatialReference oSRS;
    1440          35 :                 oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1441          35 :                 if (!osCRS.empty() && !osLowerCorner.empty() &&
    1442         105 :                     !osUpperCorner.empty() &&
    1443          70 :                     oSRS.SetFromUserInput(FixCRSName(osCRS)) == OGRERR_NONE)
    1444             :                 {
    1445             :                     const bool bSwap =
    1446          58 :                         !STARTS_WITH_CI(osCRS, "EPSG:") &&
    1447          23 :                         (CPL_TO_BOOL(oSRS.EPSGTreatsAsLatLong()) ||
    1448          10 :                          CPL_TO_BOOL(oSRS.EPSGTreatsAsNorthingEasting()));
    1449          35 :                     char **papszLC = CSLTokenizeString(osLowerCorner);
    1450          35 :                     char **papszUC = CSLTokenizeString(osUpperCorner);
    1451          35 :                     if (CSLCount(papszLC) == 2 && CSLCount(papszUC) == 2)
    1452             :                     {
    1453          35 :                         OGREnvelope sEnvelope;
    1454          35 :                         sEnvelope.MinX = CPLAtof(papszLC[(bSwap) ? 1 : 0]);
    1455          35 :                         sEnvelope.MinY = CPLAtof(papszLC[(bSwap) ? 0 : 1]);
    1456          35 :                         sEnvelope.MaxX = CPLAtof(papszUC[(bSwap) ? 1 : 0]);
    1457          35 :                         sEnvelope.MaxY = CPLAtof(papszUC[(bSwap) ? 0 : 1]);
    1458             : 
    1459          38 :                         if (bSwap && oSRS.IsGeographic() &&
    1460           3 :                             (std::fabs(sEnvelope.MinY) > 90 ||
    1461           3 :                              std::fabs(sEnvelope.MaxY) > 90))
    1462             :                         {
    1463           0 :                             if (!bHasWarnedAutoSwapBoundingBox)
    1464             :                             {
    1465           0 :                                 bHasWarnedAutoSwapBoundingBox = true;
    1466           0 :                                 CPLError(
    1467             :                                     CE_Warning, CPLE_AppDefined,
    1468             :                                     "Auto-correcting wrongly swapped "
    1469             :                                     "ows:%s coordinates. "
    1470             :                                     "They should be in latitude, longitude "
    1471             :                                     "order "
    1472             :                                     "but are presented in longitude, latitude "
    1473             :                                     "order. "
    1474             :                                     "This should be reported to the server "
    1475             :                                     "administrator.",
    1476             :                                     psSubIter->pszValue);
    1477             :                             }
    1478           0 :                             std::swap(sEnvelope.MinX, sEnvelope.MinY);
    1479           0 :                             std::swap(sEnvelope.MaxX, sEnvelope.MaxY);
    1480             :                         }
    1481             : 
    1482          35 :                         aoMapBoundingBox[osCRS] = sEnvelope;
    1483             :                     }
    1484          35 :                     CSLDestroy(papszLC);
    1485          35 :                     CSLDestroy(papszUC);
    1486             :                 }
    1487             :             }
    1488         384 :             else if (strcmp(osSelectLayer, pszIdentifier) == 0 &&
    1489         192 :                      strcmp(psSubIter->pszValue, "ResourceURL") == 0)
    1490             :             {
    1491          72 :                 if (EQUAL(CPLGetXMLValue(psSubIter, "resourceType", ""),
    1492             :                           "tile"))
    1493             :                 {
    1494             :                     const char *pszFormat =
    1495          55 :                         CPLGetXMLValue(psSubIter, "format", "");
    1496          55 :                     if (!osTileFormat.empty() &&
    1497           0 :                         strcmp(osTileFormat, pszFormat) != 0)
    1498           0 :                         continue;
    1499          55 :                     if (osURLTileTemplate.empty())
    1500             :                         osURLTileTemplate =
    1501          55 :                             CPLGetXMLValue(psSubIter, "template", "");
    1502             :                 }
    1503          17 :                 else if (EQUAL(CPLGetXMLValue(psSubIter, "resourceType", ""),
    1504             :                                "FeatureInfo"))
    1505             :                 {
    1506             :                     const char *pszFormat =
    1507          17 :                         CPLGetXMLValue(psSubIter, "format", "");
    1508          17 :                     if (!osInfoFormat.empty() &&
    1509           0 :                         strcmp(osInfoFormat, pszFormat) != 0)
    1510           0 :                         continue;
    1511          17 :                     if (osURLFeatureInfoTemplate.empty())
    1512             :                         osURLFeatureInfoTemplate =
    1513          17 :                             CPLGetXMLValue(psSubIter, "template", "");
    1514             :                 }
    1515             :             }
    1516             :         }
    1517         122 :         if (strcmp(osSelectLayer, pszIdentifier) == 0 &&
    1518         122 :             osSelectStyle.empty() && !aosStylesIdentifier.empty())
    1519             :         {
    1520          41 :             osSelectStyle = aosStylesIdentifier[0];
    1521             :         }
    1522         123 :         for (size_t i = 0; i < aosTMS.size(); i++)
    1523             :         {
    1524         131 :             for (size_t j = 0; j < aosStylesIdentifier.size(); j++)
    1525             :             {
    1526          69 :                 int nIdx = 1 + aosSubDatasets.size() / 2;
    1527         138 :                 CPLString osName(osCapabilitiesFilename);
    1528          69 :                 osName += ",layer=";
    1529          69 :                 osName += QuoteIfNecessary(pszIdentifier);
    1530          69 :                 if (aosTMS.size() > 1)
    1531             :                 {
    1532          20 :                     osName += ",tilematrixset=";
    1533          20 :                     osName += QuoteIfNecessary(aosTMS[i]);
    1534             :                 }
    1535          69 :                 if (aosStylesIdentifier.size() > 1)
    1536             :                 {
    1537          16 :                     osName += ",style=";
    1538          16 :                     osName += QuoteIfNecessary(aosStylesIdentifier[j]);
    1539             :                 }
    1540             :                 aosSubDatasets.AddNameValue(
    1541          69 :                     CPLSPrintf("SUBDATASET_%d_NAME", nIdx), osName);
    1542             : 
    1543         138 :                 CPLString osDesc("Layer ");
    1544          69 :                 osDesc += pszTitle ? pszTitle : pszIdentifier;
    1545          69 :                 if (aosTMS.size() > 1)
    1546             :                 {
    1547          20 :                     osDesc += ", tile matrix set ";
    1548          20 :                     osDesc += aosTMS[i];
    1549             :                 }
    1550          69 :                 if (aosStylesIdentifier.size() > 1)
    1551             :                 {
    1552          16 :                     osDesc += ", style ";
    1553          16 :                     osDesc += QuoteIfNecessary(aosStylesTitle[j]);
    1554             :                 }
    1555             :                 aosSubDatasets.AddNameValue(
    1556          69 :                     CPLSPrintf("SUBDATASET_%d_DESC", nIdx), osDesc);
    1557             :             }
    1558             :         }
    1559          61 :         if (!aosTMS.empty() && !aosStylesIdentifier.empty())
    1560          55 :             nLayerCount++;
    1561             :         else
    1562           6 :             CPLError(CE_Failure, CPLE_AppDefined,
    1563             :                      "Missing TileMatrixSetLink and/or Style");
    1564             :     }
    1565             : 
    1566          63 :     if (nLayerCount == 0)
    1567             :     {
    1568           8 :         CPLDestroyXMLNode(psXML);
    1569           8 :         return nullptr;
    1570             :     }
    1571             : 
    1572          55 :     WMTSDataset *poDS = new WMTSDataset();
    1573             : 
    1574          55 :     if (aosSubDatasets.size() > 2)
    1575           6 :         poDS->SetMetadata(aosSubDatasets.List(), "SUBDATASETS");
    1576             : 
    1577          55 :     if (nLayerCount == 1)
    1578             :     {
    1579          55 :         if (!osSelectLayerTitle.empty())
    1580          35 :             poDS->SetMetadataItem("TITLE", osSelectLayerTitle);
    1581          55 :         if (!osSelectLayerAbstract.empty())
    1582          16 :             poDS->SetMetadataItem("ABSTRACT", osSelectLayerAbstract);
    1583             : 
    1584          55 :         poDS->m_aosHTTPOptions = BuildHTTPRequestOpts(osOtherXML);
    1585          55 :         poDS->osLayer = osSelectLayer;
    1586          55 :         poDS->osTMS = osSelectTMS;
    1587             : 
    1588          55 :         WMTSTileMatrixSet oTMS;
    1589          55 :         if (!ReadTMS(psContents, osSelectTMS, osMaxTileMatrixIdentifier,
    1590             :                      nUserMaxZoomLevel, oTMS, bHasWarnedAutoSwap))
    1591             :         {
    1592           7 :             CPLDestroyXMLNode(psXML);
    1593           7 :             delete poDS;
    1594           7 :             return nullptr;
    1595             :         }
    1596             : 
    1597          96 :         const char *pszExtentMethod = CSLFetchNameValueDef(
    1598          48 :             poOpenInfo->papszOpenOptions, "EXTENT_METHOD", "AUTO");
    1599          48 :         ExtentMethod eExtentMethod = AUTO;
    1600          48 :         if (EQUAL(pszExtentMethod, "LAYER_BBOX"))
    1601           0 :             eExtentMethod = LAYER_BBOX;
    1602          48 :         else if (EQUAL(pszExtentMethod, "TILE_MATRIX_SET"))
    1603           0 :             eExtentMethod = TILE_MATRIX_SET;
    1604          48 :         else if (EQUAL(pszExtentMethod, "MOST_PRECISE_TILE_MATRIX"))
    1605           0 :             eExtentMethod = MOST_PRECISE_TILE_MATRIX;
    1606             : 
    1607          48 :         bool bAOIFromLayer = false;
    1608             : 
    1609             :         // Use in priority layer bounding box expressed in the SRS of the TMS
    1610          48 :         if ((!bHasAOI || bExtendBeyondDateLine) &&
    1611          96 :             (eExtentMethod == AUTO || eExtentMethod == LAYER_BBOX) &&
    1612          82 :             aoMapBoundingBox.find(oTMS.osSRS) != aoMapBoundingBox.end())
    1613             :         {
    1614           4 :             if (!bHasAOI)
    1615             :             {
    1616           4 :                 sAOI = aoMapBoundingBox[oTMS.osSRS];
    1617           4 :                 bAOIFromLayer = true;
    1618           4 :                 bHasAOI = TRUE;
    1619             :             }
    1620             : 
    1621           4 :             int bRecomputeAOI = FALSE;
    1622           4 :             if (bExtendBeyondDateLine)
    1623             :             {
    1624           1 :                 bExtendBeyondDateLine = FALSE;
    1625             : 
    1626           2 :                 OGRSpatialReference oWGS84;
    1627           1 :                 oWGS84.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
    1628           1 :                 oWGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1629             :                 OGRCoordinateTransformation *poCT =
    1630           1 :                     OGRCreateCoordinateTransformation(&oTMS.oSRS, &oWGS84);
    1631           1 :                 if (poCT != nullptr)
    1632             :                 {
    1633           1 :                     double dfX1 = sAOI.MinX;
    1634           1 :                     double dfY1 = sAOI.MinY;
    1635           1 :                     double dfX2 = sAOI.MaxX;
    1636           1 :                     double dfY2 = sAOI.MaxY;
    1637           2 :                     if (poCT->Transform(1, &dfX1, &dfY1) &&
    1638           1 :                         poCT->Transform(1, &dfX2, &dfY2))
    1639             :                     {
    1640           1 :                         if (fabs(dfX1 + 180) < 1e-8 && fabs(dfX2 - 180) < 1e-8)
    1641             :                         {
    1642           1 :                             bExtendBeyondDateLine = TRUE;
    1643           1 :                             bRecomputeAOI = TRUE;
    1644             :                         }
    1645           0 :                         else if (dfX2 < dfX1)
    1646             :                         {
    1647           0 :                             bExtendBeyondDateLine = TRUE;
    1648             :                         }
    1649             :                         else
    1650             :                         {
    1651           0 :                             CPLError(
    1652             :                                 CE_Warning, CPLE_AppDefined,
    1653             :                                 "ExtendBeyondDateLine disabled, since "
    1654             :                                 "longitudes of %s "
    1655             :                                 "BoundingBox do not span from -180 to 180 but "
    1656             :                                 "from %.16g to %.16g, "
    1657             :                                 "or longitude of upper right corner is not "
    1658             :                                 "lesser than the one of lower left corner",
    1659             :                                 oTMS.osSRS.c_str(), dfX1, dfX2);
    1660             :                         }
    1661             :                     }
    1662           1 :                     delete poCT;
    1663             :                 }
    1664             :             }
    1665           4 :             if (bExtendBeyondDateLine && bRecomputeAOI)
    1666             :             {
    1667           1 :                 bExtendBeyondDateLine = FALSE;
    1668             : 
    1669             :                 std::map<CPLString, OGREnvelope>::iterator oIter =
    1670           1 :                     aoMapBoundingBox.begin();
    1671           2 :                 for (; oIter != aoMapBoundingBox.end(); ++oIter)
    1672             :                 {
    1673           2 :                     OGRSpatialReference oSRS;
    1674           4 :                     if (oSRS.SetFromUserInput(
    1675           4 :                             FixCRSName(oIter->first),
    1676             :                             OGRSpatialReference::
    1677           2 :                                 SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
    1678             :                         OGRERR_NONE)
    1679             :                     {
    1680           2 :                         OGRSpatialReference oWGS84;
    1681           2 :                         oWGS84.SetFromUserInput(SRS_WKT_WGS84_LAT_LONG);
    1682           2 :                         oWGS84.SetAxisMappingStrategy(
    1683             :                             OAMS_TRADITIONAL_GIS_ORDER);
    1684             :                         auto poCT =
    1685             :                             std::unique_ptr<OGRCoordinateTransformation>(
    1686             :                                 OGRCreateCoordinateTransformation(&oSRS,
    1687           2 :                                                                   &oWGS84));
    1688           2 :                         double dfX1 = oIter->second.MinX;
    1689           2 :                         double dfY1 = oIter->second.MinY;
    1690           2 :                         double dfX2 = oIter->second.MaxX;
    1691           2 :                         double dfY2 = oIter->second.MaxY;
    1692           2 :                         if (poCT != nullptr &&
    1693           2 :                             poCT->Transform(1, &dfX1, &dfY1) &&
    1694           4 :                             poCT->Transform(1, &dfX2, &dfY2) && dfX2 < dfX1)
    1695             :                         {
    1696           1 :                             dfX2 += 360;
    1697           2 :                             OGRSpatialReference oWGS84_with_over;
    1698           1 :                             oWGS84_with_over.SetFromUserInput(
    1699             :                                 "+proj=longlat +datum=WGS84 +over +wktext");
    1700           1 :                             char *pszProj4 = nullptr;
    1701           1 :                             oTMS.oSRS.exportToProj4(&pszProj4);
    1702           1 :                             oSRS.SetFromUserInput(
    1703             :                                 CPLSPrintf("%s +over +wktext", pszProj4));
    1704           1 :                             CPLFree(pszProj4);
    1705           1 :                             poCT.reset(OGRCreateCoordinateTransformation(
    1706             :                                 &oWGS84_with_over, &oSRS));
    1707           2 :                             if (poCT && poCT->Transform(1, &dfX1, &dfY1) &&
    1708           1 :                                 poCT->Transform(1, &dfX2, &dfY2))
    1709             :                             {
    1710           1 :                                 bExtendBeyondDateLine = TRUE;
    1711           1 :                                 sAOI.MinX = std::min(dfX1, dfX2);
    1712           1 :                                 sAOI.MinY = std::min(dfY1, dfY2);
    1713           1 :                                 sAOI.MaxX = std::max(dfX1, dfX2);
    1714           1 :                                 sAOI.MaxY = std::max(dfY1, dfY2);
    1715           1 :                                 CPLDebug("WMTS",
    1716             :                                          "ExtendBeyondDateLine using %s "
    1717             :                                          "bounding box",
    1718           1 :                                          oIter->first.c_str());
    1719             :                             }
    1720           1 :                             break;
    1721             :                         }
    1722             :                     }
    1723             :                 }
    1724             :             }
    1725             :         }
    1726             :         else
    1727             :         {
    1728          44 :             if (bExtendBeyondDateLine)
    1729             :             {
    1730           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
    1731             :                          "ExtendBeyondDateLine disabled, since BoundingBox of "
    1732             :                          "%s is missing",
    1733             :                          oTMS.osSRS.c_str());
    1734           0 :                 bExtendBeyondDateLine = FALSE;
    1735             :             }
    1736             :         }
    1737             : 
    1738             :         // Otherwise default to reproject a layer bounding box expressed in
    1739             :         // another SRS
    1740          48 :         if (!bHasAOI && !aoMapBoundingBox.empty() &&
    1741           0 :             (eExtentMethod == AUTO || eExtentMethod == LAYER_BBOX))
    1742             :         {
    1743             :             std::map<CPLString, OGREnvelope>::iterator oIter =
    1744          13 :                 aoMapBoundingBox.begin();
    1745          13 :             for (; oIter != aoMapBoundingBox.end(); ++oIter)
    1746             :             {
    1747          13 :                 OGRSpatialReference oSRS;
    1748          13 :                 oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
    1749          26 :                 if (oSRS.SetFromUserInput(
    1750          26 :                         FixCRSName(oIter->first),
    1751             :                         OGRSpatialReference::
    1752          13 :                             SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
    1753             :                     OGRERR_NONE)
    1754             :                 {
    1755             :                     // Check if this doesn't match the most precise tile matrix
    1756             :                     // by densifying its contour
    1757          13 :                     const WMTSTileMatrix &oTM = oTMS.aoTM.back();
    1758             : 
    1759          13 :                     bool bMatchFound = false;
    1760             :                     const char *pszProjectionTMS =
    1761          13 :                         oTMS.oSRS.GetAttrValue("PROJECTION");
    1762             :                     const char *pszProjectionBBOX =
    1763          13 :                         oSRS.GetAttrValue("PROJECTION");
    1764          13 :                     const bool bIsTMerc =
    1765          12 :                         (pszProjectionTMS != nullptr &&
    1766          13 :                          EQUAL(pszProjectionTMS, SRS_PT_TRANSVERSE_MERCATOR)) ||
    1767           0 :                         (pszProjectionBBOX != nullptr &&
    1768           0 :                          EQUAL(pszProjectionBBOX, SRS_PT_TRANSVERSE_MERCATOR));
    1769             :                     // If one of the 2 SRS is a TMerc, try with classical tmerc
    1770             :                     // or etmerc.
    1771          23 :                     for (int j = 0; j < (bIsTMerc ? 2 : 1); j++)
    1772             :                     {
    1773             :                         CPLString osOldVal = CPLGetThreadLocalConfigOption(
    1774          17 :                             "OSR_USE_APPROX_TMERC", "");
    1775          17 :                         if (bIsTMerc)
    1776             :                         {
    1777           8 :                             CPLSetThreadLocalConfigOption(
    1778             :                                 "OSR_USE_APPROX_TMERC",
    1779             :                                 (j == 0) ? "NO" : "YES");
    1780             :                         }
    1781             :                         OGRCoordinateTransformation *poRevCT =
    1782          17 :                             OGRCreateCoordinateTransformation(&oTMS.oSRS,
    1783             :                                                               &oSRS);
    1784          17 :                         if (bIsTMerc)
    1785             :                         {
    1786           8 :                             CPLSetThreadLocalConfigOption(
    1787             :                                 "OSR_USE_APPROX_TMERC",
    1788           8 :                                 osOldVal.empty() ? nullptr : osOldVal.c_str());
    1789             :                         }
    1790          17 :                         if (poRevCT != nullptr)
    1791             :                         {
    1792          17 :                             const auto sTMExtent = oTM.GetExtent();
    1793          17 :                             const double dfX0 = sTMExtent.MinX;
    1794          17 :                             const double dfY1 = sTMExtent.MaxY;
    1795          17 :                             const double dfX1 = sTMExtent.MaxX;
    1796          17 :                             const double dfY0 = sTMExtent.MinY;
    1797          17 :                             double dfXMin =
    1798             :                                 std::numeric_limits<double>::infinity();
    1799          17 :                             double dfYMin =
    1800             :                                 std::numeric_limits<double>::infinity();
    1801          17 :                             double dfXMax =
    1802             :                                 -std::numeric_limits<double>::infinity();
    1803          17 :                             double dfYMax =
    1804             :                                 -std::numeric_limits<double>::infinity();
    1805             : 
    1806          17 :                             const int NSTEPS = 20;
    1807         374 :                             for (int i = 0; i <= NSTEPS; i++)
    1808             :                             {
    1809         357 :                                 double dfX = dfX0 + (dfX1 - dfX0) * i / NSTEPS;
    1810         357 :                                 double dfY = dfY0;
    1811         357 :                                 if (poRevCT->Transform(1, &dfX, &dfY))
    1812             :                                 {
    1813         357 :                                     dfXMin = std::min(dfXMin, dfX);
    1814         357 :                                     dfYMin = std::min(dfYMin, dfY);
    1815         357 :                                     dfXMax = std::max(dfXMax, dfX);
    1816         357 :                                     dfYMax = std::max(dfYMax, dfY);
    1817             :                                 }
    1818             : 
    1819         357 :                                 dfX = dfX0 + (dfX1 - dfX0) * i / NSTEPS;
    1820         357 :                                 dfY = dfY1;
    1821         357 :                                 if (poRevCT->Transform(1, &dfX, &dfY))
    1822             :                                 {
    1823         357 :                                     dfXMin = std::min(dfXMin, dfX);
    1824         357 :                                     dfYMin = std::min(dfYMin, dfY);
    1825         357 :                                     dfXMax = std::max(dfXMax, dfX);
    1826         357 :                                     dfYMax = std::max(dfYMax, dfY);
    1827             :                                 }
    1828             : 
    1829         357 :                                 dfX = dfX0;
    1830         357 :                                 dfY = dfY0 + (dfY1 - dfY0) * i / NSTEPS;
    1831         357 :                                 if (poRevCT->Transform(1, &dfX, &dfY))
    1832             :                                 {
    1833         357 :                                     dfXMin = std::min(dfXMin, dfX);
    1834         357 :                                     dfYMin = std::min(dfYMin, dfY);
    1835         357 :                                     dfXMax = std::max(dfXMax, dfX);
    1836         357 :                                     dfYMax = std::max(dfYMax, dfY);
    1837             :                                 }
    1838             : 
    1839         357 :                                 dfX = dfX1;
    1840         357 :                                 dfY = dfY0 + (dfY1 - dfY0) * i / NSTEPS;
    1841         357 :                                 if (poRevCT->Transform(1, &dfX, &dfY))
    1842             :                                 {
    1843         357 :                                     dfXMin = std::min(dfXMin, dfX);
    1844         357 :                                     dfYMin = std::min(dfYMin, dfY);
    1845         357 :                                     dfXMax = std::max(dfXMax, dfX);
    1846         357 :                                     dfYMax = std::max(dfYMax, dfY);
    1847             :                                 }
    1848             :                             }
    1849             : 
    1850          17 :                             delete poRevCT;
    1851             : #ifdef DEBUG_VERBOSE
    1852             :                             CPLDebug(
    1853             :                                 "WMTS",
    1854             :                                 "Reprojected densified bbox of most "
    1855             :                                 "precise tile matrix in %s: %.8g %8g %8g %8g",
    1856             :                                 oIter->first.c_str(), dfXMin, dfYMin, dfXMax,
    1857             :                                 dfYMax);
    1858             : #endif
    1859          17 :                             if (fabs(oIter->second.MinX - dfXMin) <
    1860          68 :                                     1e-5 * std::max(fabs(oIter->second.MinX),
    1861          17 :                                                     fabs(dfXMin)) &&
    1862          10 :                                 fabs(oIter->second.MinY - dfYMin) <
    1863          10 :                                     1e-5 * std::max(fabs(oIter->second.MinY),
    1864          10 :                                                     fabs(dfYMin)) &&
    1865          10 :                                 fabs(oIter->second.MaxX - dfXMax) <
    1866          10 :                                     1e-5 * std::max(fabs(oIter->second.MaxX),
    1867          37 :                                                     fabs(dfXMax)) &&
    1868          10 :                                 fabs(oIter->second.MaxY - dfYMax) <
    1869          10 :                                     1e-5 * std::max(fabs(oIter->second.MaxY),
    1870          27 :                                                     fabs(dfYMax)))
    1871             :                             {
    1872           7 :                                 bMatchFound = true;
    1873             : #ifdef DEBUG_VERBOSE
    1874             :                                 CPLDebug("WMTS",
    1875             :                                          "Matches layer bounding box, so "
    1876             :                                          "that one is not significant");
    1877             : #endif
    1878           7 :                                 break;
    1879             :                             }
    1880             :                         }
    1881             :                     }
    1882             : 
    1883          13 :                     if (bMatchFound)
    1884             :                     {
    1885           7 :                         if (eExtentMethod == LAYER_BBOX)
    1886           0 :                             eExtentMethod = MOST_PRECISE_TILE_MATRIX;
    1887           7 :                         break;
    1888             :                     }
    1889             : 
    1890             :                     // Otherwise try to reproject the bounding box of the
    1891             :                     // layer from its SRS to the TMS SRS. Except in some cases
    1892             :                     // where this would result in non-sense. (this could be
    1893             :                     // improved !)
    1894           8 :                     if (!(bIsTMerc && oSRS.IsGeographic() &&
    1895           2 :                           fabs(oIter->second.MinX - -180) < 1e-8 &&
    1896           1 :                           fabs(oIter->second.MaxX - 180) < 1e-8))
    1897             :                     {
    1898             :                         OGRCoordinateTransformation *poCT =
    1899           5 :                             OGRCreateCoordinateTransformation(&oSRS,
    1900             :                                                               &oTMS.oSRS);
    1901           5 :                         if (poCT != nullptr)
    1902             :                         {
    1903           5 :                             double dfX1 = oIter->second.MinX;
    1904           5 :                             double dfY1 = oIter->second.MinY;
    1905           5 :                             double dfX2 = oIter->second.MaxX;
    1906           5 :                             double dfY2 = oIter->second.MinY;
    1907           5 :                             double dfX3 = oIter->second.MaxX;
    1908           5 :                             double dfY3 = oIter->second.MaxY;
    1909           5 :                             double dfX4 = oIter->second.MinX;
    1910           5 :                             double dfY4 = oIter->second.MaxY;
    1911           5 :                             if (poCT->Transform(1, &dfX1, &dfY1) &&
    1912           5 :                                 poCT->Transform(1, &dfX2, &dfY2) &&
    1913          15 :                                 poCT->Transform(1, &dfX3, &dfY3) &&
    1914           5 :                                 poCT->Transform(1, &dfX4, &dfY4))
    1915             :                             {
    1916           5 :                                 sAOI.MinX = std::min(std::min(dfX1, dfX2),
    1917           5 :                                                      std::min(dfX3, dfX4));
    1918           5 :                                 sAOI.MinY = std::min(std::min(dfY1, dfY2),
    1919           5 :                                                      std::min(dfY3, dfY4));
    1920           5 :                                 sAOI.MaxX = std::max(std::max(dfX1, dfX2),
    1921           5 :                                                      std::max(dfX3, dfX4));
    1922           5 :                                 sAOI.MaxY = std::max(std::max(dfY1, dfY2),
    1923           5 :                                                      std::max(dfY3, dfY4));
    1924           5 :                                 bHasAOI = TRUE;
    1925           5 :                                 bAOIFromLayer = true;
    1926             :                             }
    1927           5 :                             delete poCT;
    1928             :                         }
    1929             :                     }
    1930           6 :                     break;
    1931             :                 }
    1932             :             }
    1933             :         }
    1934             : 
    1935             :         // Clip the computed AOI with the union of the extent of the tile
    1936             :         // matrices
    1937          48 :         if (bHasAOI && !bExtendBeyondDateLine)
    1938             :         {
    1939          22 :             OGREnvelope sUnionTM;
    1940         104 :             for (const WMTSTileMatrix &oTM : oTMS.aoTM)
    1941             :             {
    1942          82 :                 if (!sUnionTM.IsInit())
    1943          22 :                     sUnionTM = oTM.GetExtent();
    1944             :                 else
    1945          60 :                     sUnionTM.Merge(oTM.GetExtent());
    1946             :             }
    1947          22 :             sAOI.Intersect(sUnionTM);
    1948             :         }
    1949             : 
    1950             :         // Otherwise default to BoundingBox of the TMS
    1951          48 :         if (!bHasAOI && oTMS.bBoundingBoxValid &&
    1952           0 :             (eExtentMethod == AUTO || eExtentMethod == TILE_MATRIX_SET))
    1953             :         {
    1954           1 :             CPLDebug("WMTS", "Using TMS bounding box as layer extent");
    1955           1 :             sAOI = oTMS.sBoundingBox;
    1956           1 :             bHasAOI = TRUE;
    1957             :         }
    1958             : 
    1959             :         // Otherwise default to implied BoundingBox of the most precise TM
    1960          48 :         if (!bHasAOI && (eExtentMethod == AUTO ||
    1961             :                          eExtentMethod == MOST_PRECISE_TILE_MATRIX))
    1962             :         {
    1963          24 :             const WMTSTileMatrix &oTM = oTMS.aoTM.back();
    1964          24 :             CPLDebug("WMTS", "Using TM level %s bounding box as layer extent",
    1965             :                      oTM.osIdentifier.c_str());
    1966             : 
    1967          24 :             sAOI = oTM.GetExtent();
    1968          24 :             bHasAOI = TRUE;
    1969             :         }
    1970             : 
    1971          48 :         if (!bHasAOI)
    1972             :         {
    1973           0 :             CPLError(CE_Failure, CPLE_AppDefined,
    1974             :                      "Could not determine raster extent");
    1975           0 :             CPLDestroyXMLNode(psXML);
    1976           0 :             delete poDS;
    1977           0 :             return nullptr;
    1978             :         }
    1979             : 
    1980          96 :         if (CPLTestBool(CSLFetchNameValueDef(
    1981          48 :                 poOpenInfo->papszOpenOptions,
    1982             :                 "CLIP_EXTENT_WITH_MOST_PRECISE_TILE_MATRIX",
    1983             :                 bAOIFromLayer ? "NO" : "YES")))
    1984             :         {
    1985             :             // Clip with implied BoundingBox of the most precise TM
    1986             :             // Useful for http://tileserver.maptiler.com/wmts
    1987          39 :             const WMTSTileMatrix &oTM = oTMS.aoTM.back();
    1988          39 :             const OGREnvelope sTMExtent = oTM.GetExtent();
    1989          39 :             OGREnvelope sAOINew(sAOI);
    1990             : 
    1991             :             // For
    1992             :             // https://data.linz.govt.nz/services;key=XXXXXXXX/wmts/1.0.0/set/69/WMTSCapabilities.xml
    1993             :             // only clip in Y since there's a warp over dateline.
    1994             :             // Update: it sems that the content of the server has changed since
    1995             :             // initial coding. So do X clipping in default mode.
    1996          39 :             if (!bExtendBeyondDateLine)
    1997             :             {
    1998          39 :                 sAOINew.MinX = std::max(sAOI.MinX, sTMExtent.MinX);
    1999          39 :                 sAOINew.MaxX = std::min(sAOI.MaxX, sTMExtent.MaxX);
    2000             :             }
    2001          39 :             sAOINew.MaxY = std::min(sAOI.MaxY, sTMExtent.MaxY);
    2002          39 :             sAOINew.MinY = std::max(sAOI.MinY, sTMExtent.MinY);
    2003          39 :             if (sAOI != sAOINew)
    2004             :             {
    2005           0 :                 CPLDebug(
    2006             :                     "WMTS",
    2007             :                     "Layer extent has been restricted from "
    2008             :                     "(%f,%f,%f,%f) to (%f,%f,%f,%f) using the "
    2009             :                     "implied bounding box of the most precise tile matrix. "
    2010             :                     "You may disable this by specifying the "
    2011             :                     "CLIP_EXTENT_WITH_MOST_PRECISE_TILE_MATRIX open option "
    2012             :                     "to NO.",
    2013             :                     sAOI.MinX, sAOI.MinY, sAOI.MaxX, sAOI.MaxY, sAOINew.MinX,
    2014             :                     sAOINew.MinY, sAOINew.MaxX, sAOINew.MaxY);
    2015             :             }
    2016          39 :             sAOI = sAOINew;
    2017             :         }
    2018             : 
    2019             :         // Clip with limits of most precise TM when available
    2020          96 :         if (CPLTestBool(CSLFetchNameValueDef(
    2021          48 :                 poOpenInfo->papszOpenOptions,
    2022             :                 "CLIP_EXTENT_WITH_MOST_PRECISE_TILE_MATRIX_LIMITS",
    2023             :                 bAOIFromLayer ? "NO" : "YES")))
    2024             :         {
    2025          40 :             const WMTSTileMatrix &oTM = oTMS.aoTM.back();
    2026          40 :             if (aoMapTileMatrixLimits.find(oTM.osIdentifier) !=
    2027          80 :                 aoMapTileMatrixLimits.end())
    2028             :             {
    2029           2 :                 OGREnvelope sAOINew(sAOI);
    2030             : 
    2031             :                 const WMTSTileMatrixLimits &oTMLimits =
    2032           2 :                     aoMapTileMatrixLimits[oTM.osIdentifier];
    2033           2 :                 const OGREnvelope sTMLimitsExtent = oTMLimits.GetExtent(oTM);
    2034           2 :                 sAOINew.Intersect(sTMLimitsExtent);
    2035             : 
    2036           2 :                 if (sAOI != sAOINew)
    2037             :                 {
    2038           2 :                     CPLDebug(
    2039             :                         "WMTS",
    2040             :                         "Layer extent has been restricted from "
    2041             :                         "(%f,%f,%f,%f) to (%f,%f,%f,%f) using the "
    2042             :                         "implied bounding box of the most precise tile matrix. "
    2043             :                         "You may disable this by specifying the "
    2044             :                         "CLIP_EXTENT_WITH_MOST_PRECISE_TILE_MATRIX_LIMITS open "
    2045             :                         "option "
    2046             :                         "to NO.",
    2047             :                         sAOI.MinX, sAOI.MinY, sAOI.MaxX, sAOI.MaxY,
    2048             :                         sAOINew.MinX, sAOINew.MinY, sAOINew.MaxX, sAOINew.MaxY);
    2049             :                 }
    2050           2 :                 sAOI = sAOINew;
    2051             :             }
    2052             :         }
    2053             : 
    2054          48 :         if (!osProjection.empty())
    2055             :         {
    2056           0 :             poDS->m_oSRS.SetFromUserInput(
    2057             :                 osProjection,
    2058             :                 OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get());
    2059             :         }
    2060          48 :         if (poDS->m_oSRS.IsEmpty())
    2061             :         {
    2062          48 :             poDS->m_oSRS = oTMS.oSRS;
    2063             :         }
    2064             : 
    2065          48 :         if (osURLTileTemplate.empty())
    2066             :         {
    2067           5 :             osURLTileTemplate = GetOperationKVPURL(psXML, "GetTile");
    2068           5 :             if (osURLTileTemplate.empty())
    2069             :             {
    2070           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
    2071             :                          "No RESTful nor KVP GetTile operation found");
    2072           1 :                 CPLDestroyXMLNode(psXML);
    2073           1 :                 delete poDS;
    2074           1 :                 return nullptr;
    2075             :             }
    2076             :             osURLTileTemplate =
    2077           4 :                 CPLURLAddKVP(osURLTileTemplate, "service", "WMTS");
    2078             :             osURLTileTemplate =
    2079           4 :                 CPLURLAddKVP(osURLTileTemplate, "request", "GetTile");
    2080             :             osURLTileTemplate =
    2081           4 :                 CPLURLAddKVP(osURLTileTemplate, "version", "1.0.0");
    2082             :             osURLTileTemplate =
    2083           4 :                 CPLURLAddKVP(osURLTileTemplate, "layer", osSelectLayer);
    2084             :             osURLTileTemplate =
    2085           4 :                 CPLURLAddKVP(osURLTileTemplate, "style", osSelectStyle);
    2086             :             osURLTileTemplate =
    2087           4 :                 CPLURLAddKVP(osURLTileTemplate, "format", osSelectTileFormat);
    2088             :             osURLTileTemplate =
    2089           4 :                 CPLURLAddKVP(osURLTileTemplate, "TileMatrixSet", osSelectTMS);
    2090           4 :             osURLTileTemplate += "&TileMatrix={TileMatrix}";
    2091           4 :             osURLTileTemplate += "&TileRow=${y}";
    2092           4 :             osURLTileTemplate += "&TileCol=${x}";
    2093             : 
    2094             :             std::map<CPLString, CPLString>::iterator oIter =
    2095           4 :                 aoMapDimensions.begin();
    2096           8 :             for (; oIter != aoMapDimensions.end(); ++oIter)
    2097             :             {
    2098          12 :                 osURLTileTemplate = CPLURLAddKVP(osURLTileTemplate,
    2099          12 :                                                  oIter->first, oIter->second);
    2100             :             }
    2101             :             // CPLDebug("WMTS", "osURLTileTemplate = %s",
    2102             :             // osURLTileTemplate.c_str());
    2103             :         }
    2104             :         else
    2105             :         {
    2106             :             osURLTileTemplate =
    2107          43 :                 Replace(osURLTileTemplate, "{Style}", osSelectStyle);
    2108             :             osURLTileTemplate =
    2109          43 :                 Replace(osURLTileTemplate, "{TileMatrixSet}", osSelectTMS);
    2110          43 :             osURLTileTemplate = Replace(osURLTileTemplate, "{TileCol}", "${x}");
    2111          43 :             osURLTileTemplate = Replace(osURLTileTemplate, "{TileRow}", "${y}");
    2112             : 
    2113             :             std::map<CPLString, CPLString>::iterator oIter =
    2114          43 :                 aoMapDimensions.begin();
    2115          57 :             for (; oIter != aoMapDimensions.end(); ++oIter)
    2116             :             {
    2117          42 :                 osURLTileTemplate = Replace(
    2118          14 :                     osURLTileTemplate, CPLSPrintf("{%s}", oIter->first.c_str()),
    2119          28 :                     oIter->second);
    2120             :             }
    2121             :         }
    2122          47 :         osURLTileTemplate += osExtraQueryParameters;
    2123             : 
    2124          47 :         if (osURLFeatureInfoTemplate.empty() && !osSelectInfoFormat.empty())
    2125             :         {
    2126             :             osURLFeatureInfoTemplate =
    2127           4 :                 GetOperationKVPURL(psXML, "GetFeatureInfo");
    2128           4 :             if (!osURLFeatureInfoTemplate.empty())
    2129             :             {
    2130             :                 osURLFeatureInfoTemplate =
    2131           4 :                     CPLURLAddKVP(osURLFeatureInfoTemplate, "service", "WMTS");
    2132           8 :                 osURLFeatureInfoTemplate = CPLURLAddKVP(
    2133           4 :                     osURLFeatureInfoTemplate, "request", "GetFeatureInfo");
    2134             :                 osURLFeatureInfoTemplate =
    2135           4 :                     CPLURLAddKVP(osURLFeatureInfoTemplate, "version", "1.0.0");
    2136           8 :                 osURLFeatureInfoTemplate = CPLURLAddKVP(
    2137           4 :                     osURLFeatureInfoTemplate, "layer", osSelectLayer);
    2138           8 :                 osURLFeatureInfoTemplate = CPLURLAddKVP(
    2139           4 :                     osURLFeatureInfoTemplate, "style", osSelectStyle);
    2140             :                 // osURLFeatureInfoTemplate =
    2141             :                 // CPLURLAddKVP(osURLFeatureInfoTemplate, "format",
    2142             :                 // osSelectTileFormat);
    2143           8 :                 osURLFeatureInfoTemplate = CPLURLAddKVP(
    2144           4 :                     osURLFeatureInfoTemplate, "InfoFormat", osSelectInfoFormat);
    2145           4 :                 osURLFeatureInfoTemplate += "&TileMatrixSet={TileMatrixSet}";
    2146           4 :                 osURLFeatureInfoTemplate += "&TileMatrix={TileMatrix}";
    2147           4 :                 osURLFeatureInfoTemplate += "&TileRow={TileRow}";
    2148           4 :                 osURLFeatureInfoTemplate += "&TileCol={TileCol}";
    2149           4 :                 osURLFeatureInfoTemplate += "&J={J}";
    2150           4 :                 osURLFeatureInfoTemplate += "&I={I}";
    2151             : 
    2152             :                 std::map<CPLString, CPLString>::iterator oIter =
    2153           4 :                     aoMapDimensions.begin();
    2154           8 :                 for (; oIter != aoMapDimensions.end(); ++oIter)
    2155             :                 {
    2156          12 :                     osURLFeatureInfoTemplate = CPLURLAddKVP(
    2157          12 :                         osURLFeatureInfoTemplate, oIter->first, oIter->second);
    2158             :                 }
    2159             :                 // CPLDebug("WMTS", "osURLFeatureInfoTemplate = %s",
    2160             :                 // osURLFeatureInfoTemplate.c_str());
    2161             :             }
    2162             :         }
    2163             :         else
    2164             :         {
    2165             :             osURLFeatureInfoTemplate =
    2166          43 :                 Replace(osURLFeatureInfoTemplate, "{Style}", osSelectStyle);
    2167             : 
    2168             :             std::map<CPLString, CPLString>::iterator oIter =
    2169          43 :                 aoMapDimensions.begin();
    2170          57 :             for (; oIter != aoMapDimensions.end(); ++oIter)
    2171             :             {
    2172          42 :                 osURLFeatureInfoTemplate = Replace(
    2173             :                     osURLFeatureInfoTemplate,
    2174          42 :                     CPLSPrintf("{%s}", oIter->first.c_str()), oIter->second);
    2175             :             }
    2176             :         }
    2177          47 :         if (!osURLFeatureInfoTemplate.empty())
    2178          18 :             osURLFeatureInfoTemplate += osExtraQueryParameters;
    2179          47 :         poDS->osURLFeatureInfoTemplate = osURLFeatureInfoTemplate;
    2180          47 :         CPL_IGNORE_RET_VAL(osURLFeatureInfoTemplate);
    2181             : 
    2182             :         // Build all TMS datasets, wrapped in VRT datasets
    2183         160 :         for (int i = static_cast<int>(oTMS.aoTM.size() - 1); i >= 0; i--)
    2184             :         {
    2185         123 :             const WMTSTileMatrix &oTM = oTMS.aoTM[i];
    2186         123 :             double dfRasterXSize = (sAOI.MaxX - sAOI.MinX) / oTM.dfPixelSize;
    2187         123 :             double dfRasterYSize = (sAOI.MaxY - sAOI.MinY) / oTM.dfPixelSize;
    2188         123 :             if (dfRasterXSize > INT_MAX || dfRasterYSize > INT_MAX)
    2189             :             {
    2190          13 :                 continue;
    2191             :             }
    2192             : 
    2193         111 :             if (poDS->apoDatasets.empty())
    2194             :             {
    2195             :                 // Align AOI on pixel boundaries with respect to TopLeftCorner
    2196             :                 // of this tile matrix
    2197          96 :                 poDS->m_gt[0] =
    2198          48 :                     oTM.dfTLX +
    2199          48 :                     floor((sAOI.MinX - oTM.dfTLX) / oTM.dfPixelSize + 1e-10) *
    2200          48 :                         oTM.dfPixelSize;
    2201          48 :                 poDS->m_gt[1] = oTM.dfPixelSize;
    2202          48 :                 poDS->m_gt[2] = 0.0;
    2203          96 :                 poDS->m_gt[3] =
    2204          48 :                     oTM.dfTLY +
    2205          48 :                     ceil((sAOI.MaxY - oTM.dfTLY) / oTM.dfPixelSize - 1e-10) *
    2206          48 :                         oTM.dfPixelSize;
    2207          48 :                 poDS->m_gt[4] = 0.0;
    2208          48 :                 poDS->m_gt[5] = -oTM.dfPixelSize;
    2209          48 :                 poDS->nRasterXSize =
    2210          48 :                     int(0.5 + (sAOI.MaxX - poDS->m_gt[0]) / oTM.dfPixelSize);
    2211          48 :                 poDS->nRasterYSize =
    2212          48 :                     int(0.5 + (poDS->m_gt[3] - sAOI.MinY) / oTM.dfPixelSize);
    2213             :             }
    2214             : 
    2215             :             const int nRasterXSize =
    2216         111 :                 int(0.5 + poDS->nRasterXSize / oTM.dfPixelSize * poDS->m_gt[1]);
    2217             :             const int nRasterYSize =
    2218         111 :                 int(0.5 + poDS->nRasterYSize / oTM.dfPixelSize * poDS->m_gt[1]);
    2219         170 :             if (!poDS->apoDatasets.empty() &&
    2220          59 :                 (nRasterXSize < 128 || nRasterYSize < 128))
    2221             :             {
    2222          10 :                 break;
    2223             :             }
    2224             :             CPLString osURL(
    2225         101 :                 Replace(osURLTileTemplate, "{TileMatrix}", oTM.osIdentifier));
    2226             : 
    2227         101 :             const double dfTileWidthUnits = oTM.dfPixelSize * oTM.nTileWidth;
    2228         101 :             const double dfTileHeightUnits = oTM.dfPixelSize * oTM.nTileHeight;
    2229             : 
    2230             :             // Get bounds of this tile matrix / tile matrix limits
    2231         101 :             auto sTMExtent = oTM.GetExtent();
    2232         101 :             if (aoMapTileMatrixLimits.find(oTM.osIdentifier) !=
    2233         202 :                 aoMapTileMatrixLimits.end())
    2234             :             {
    2235             :                 const WMTSTileMatrixLimits &oTMLimits =
    2236           2 :                     aoMapTileMatrixLimits[oTM.osIdentifier];
    2237           2 :                 sTMExtent.Intersect(oTMLimits.GetExtent(oTM));
    2238             :             }
    2239             : 
    2240             :             // Compute the shift in terms of tiles between AOI and TM origin
    2241             :             const int nTileX =
    2242         101 :                 static_cast<int>(floor(std::max(sTMExtent.MinX, poDS->m_gt[0]) -
    2243         101 :                                        oTM.dfTLX + 1e-10) /
    2244         101 :                                  dfTileWidthUnits);
    2245             :             const int nTileY = static_cast<int>(
    2246         101 :                 floor(oTM.dfTLY - std::min(poDS->m_gt[3], sTMExtent.MaxY) +
    2247         101 :                       1e-10) /
    2248         101 :                 dfTileHeightUnits);
    2249             : 
    2250             :             // Compute extent of this zoom level slightly larger than the AOI
    2251             :             // and aligned on tile boundaries at this TM
    2252         101 :             double dfULX = oTM.dfTLX + nTileX * dfTileWidthUnits;
    2253         101 :             double dfULY = oTM.dfTLY - nTileY * dfTileHeightUnits;
    2254         101 :             double dfLRX = poDS->m_gt[0] + poDS->nRasterXSize * poDS->m_gt[1];
    2255         101 :             double dfLRY = poDS->m_gt[3] + poDS->nRasterYSize * poDS->m_gt[5];
    2256         101 :             dfLRX = dfULX + ceil((dfLRX - dfULX) / dfTileWidthUnits - 1e-10) *
    2257             :                                 dfTileWidthUnits;
    2258         101 :             dfLRY = dfULY + floor((dfLRY - dfULY) / dfTileHeightUnits + 1e-10) *
    2259             :                                 dfTileHeightUnits;
    2260             : 
    2261             :             // Clip TMS extent to the one of this TM
    2262         101 :             if (!bExtendBeyondDateLine)
    2263          99 :                 dfLRX = std::min(dfLRX, sTMExtent.MaxX);
    2264         101 :             dfLRY = std::max(dfLRY, sTMExtent.MinY);
    2265             : 
    2266         101 :             const double dfSizeX = 0.5 + (dfLRX - dfULX) / oTM.dfPixelSize;
    2267         101 :             const double dfSizeY = 0.5 + (dfULY - dfLRY) / oTM.dfPixelSize;
    2268         101 :             if (dfSizeX > INT_MAX || dfSizeY > INT_MAX)
    2269             :             {
    2270           1 :                 continue;
    2271             :             }
    2272         100 :             if (poDS->apoDatasets.empty())
    2273             :             {
    2274          47 :                 CPLDebug("WMTS", "Using tilematrix=%s (zoom level %d)",
    2275          47 :                          oTMS.aoTM[i].osIdentifier.c_str(), i);
    2276          47 :                 oTMS.aoTM.resize(1 + i);
    2277          47 :                 poDS->oTMS = oTMS;
    2278             :             }
    2279             : 
    2280         100 :             const int nSizeX = static_cast<int>(dfSizeX);
    2281         100 :             const int nSizeY = static_cast<int>(dfSizeY);
    2282             : 
    2283         100 :             const double dfDateLineX =
    2284         100 :                 oTM.dfTLX + oTM.nMatrixWidth * dfTileWidthUnits;
    2285         100 :             const int nSizeX1 =
    2286         100 :                 int(0.5 + (dfDateLineX - dfULX) / oTM.dfPixelSize);
    2287         100 :             const int nSizeX2 =
    2288         100 :                 int(0.5 + (dfLRX - dfDateLineX) / oTM.dfPixelSize);
    2289         100 :             if (bExtendBeyondDateLine && dfDateLineX > dfLRX)
    2290             :             {
    2291           0 :                 CPLDebug("WMTS", "ExtendBeyondDateLine ignored in that case");
    2292           0 :                 bExtendBeyondDateLine = FALSE;
    2293             :             }
    2294             : 
    2295             : #define WMS_TMS_TEMPLATE                                                       \
    2296             :     "<GDAL_WMS>"                                                               \
    2297             :     "<Service name=\"TMS\">"                                                   \
    2298             :     "    <ServerUrl>%s</ServerUrl>"                                            \
    2299             :     "</Service>"                                                               \
    2300             :     "<DataWindow>"                                                             \
    2301             :     "    <UpperLeftX>%.16g</UpperLeftX>"                                       \
    2302             :     "    <UpperLeftY>%.16g</UpperLeftY>"                                       \
    2303             :     "    <LowerRightX>%.16g</LowerRightX>"                                     \
    2304             :     "    <LowerRightY>%.16g</LowerRightY>"                                     \
    2305             :     "    <TileLevel>0</TileLevel>"                                             \
    2306             :     "    <TileX>%d</TileX>"                                                    \
    2307             :     "    <TileY>%d</TileY>"                                                    \
    2308             :     "    <SizeX>%d</SizeX>"                                                    \
    2309             :     "    <SizeY>%d</SizeY>"                                                    \
    2310             :     "    <YOrigin>top</YOrigin>"                                               \
    2311             :     "</DataWindow>"                                                            \
    2312             :     "<BlockSizeX>%d</BlockSizeX>"                                              \
    2313             :     "<BlockSizeY>%d</BlockSizeY>"                                              \
    2314             :     "<BandsCount>%d</BandsCount>"                                              \
    2315             :     "<DataType>%s</DataType>"                                                  \
    2316             :     "%s"                                                                       \
    2317             :     "</GDAL_WMS>"
    2318             : 
    2319             :             CPLString osStr(CPLSPrintf(
    2320         100 :                 WMS_TMS_TEMPLATE, WMTSEscapeXML(osURL).c_str(), dfULX, dfULY,
    2321             :                 (bExtendBeyondDateLine) ? dfDateLineX : dfLRX, dfLRY, nTileX,
    2322             :                 nTileY, (bExtendBeyondDateLine) ? nSizeX1 : nSizeX, nSizeY,
    2323         100 :                 oTM.nTileWidth, oTM.nTileHeight, nBands,
    2324         200 :                 GDALGetDataTypeName(eDataType), osOtherXML.c_str()));
    2325         100 :             const auto eLastErrorType = CPLGetLastErrorType();
    2326         100 :             const auto eLastErrorNum = CPLGetLastErrorNo();
    2327         100 :             const std::string osLastErrorMsg = CPLGetLastErrorMsg();
    2328         100 :             GDALDataset *poWMSDS = GDALDataset::Open(
    2329             :                 osStr, GDAL_OF_RASTER | GDAL_OF_SHARED | GDAL_OF_VERBOSE_ERROR,
    2330             :                 nullptr, nullptr, nullptr);
    2331         100 :             if (poWMSDS == nullptr)
    2332             :             {
    2333           0 :                 CPLDestroyXMLNode(psXML);
    2334           0 :                 delete poDS;
    2335           0 :                 return nullptr;
    2336             :             }
    2337             :             // Restore error state to what it was prior to WMS dataset opening
    2338             :             // if WMS dataset opening did not cause any new error to be emitted
    2339         100 :             if (CPLGetLastErrorType() == CE_None)
    2340         100 :                 CPLErrorSetState(eLastErrorType, eLastErrorNum,
    2341             :                                  osLastErrorMsg.c_str());
    2342             : 
    2343         100 :             VRTDatasetH hVRTDS = VRTCreate(nRasterXSize, nRasterYSize);
    2344         500 :             for (int iBand = 1; iBand <= nBands; iBand++)
    2345             :             {
    2346         400 :                 VRTAddBand(hVRTDS, eDataType, nullptr);
    2347             :             }
    2348             : 
    2349             :             int nSrcXOff, nSrcYOff, nDstXOff, nDstYOff;
    2350             : 
    2351         100 :             nSrcXOff = 0;
    2352         100 :             nDstXOff = static_cast<int>(
    2353         100 :                 std::round((dfULX - poDS->m_gt[0]) / oTM.dfPixelSize));
    2354             : 
    2355         100 :             nSrcYOff = 0;
    2356         100 :             nDstYOff = static_cast<int>(
    2357         100 :                 std::round((poDS->m_gt[3] - dfULY) / oTM.dfPixelSize));
    2358             : 
    2359         100 :             if (bExtendBeyondDateLine)
    2360             :             {
    2361             :                 int nSrcXOff2, nDstXOff2;
    2362             : 
    2363           2 :                 nSrcXOff2 = 0;
    2364           2 :                 nDstXOff2 = static_cast<int>(std::round(
    2365           2 :                     (dfDateLineX - poDS->m_gt[0]) / oTM.dfPixelSize));
    2366             : 
    2367             :                 osStr = CPLSPrintf(
    2368           2 :                     WMS_TMS_TEMPLATE, WMTSEscapeXML(osURL).c_str(),
    2369           2 :                     -dfDateLineX, dfULY, dfLRX - 2 * dfDateLineX, dfLRY, 0,
    2370           2 :                     nTileY, nSizeX2, nSizeY, oTM.nTileWidth, oTM.nTileHeight,
    2371           4 :                     nBands, GDALGetDataTypeName(eDataType), osOtherXML.c_str());
    2372             : 
    2373             :                 GDALDataset *poWMSDS2 =
    2374           2 :                     GDALDataset::Open(osStr, GDAL_OF_RASTER | GDAL_OF_SHARED,
    2375             :                                       nullptr, nullptr, nullptr);
    2376           2 :                 CPLAssert(poWMSDS2);
    2377             : 
    2378          10 :                 for (int iBand = 1; iBand <= nBands; iBand++)
    2379             :                 {
    2380             :                     VRTSourcedRasterBandH hVRTBand =
    2381           8 :                         reinterpret_cast<VRTSourcedRasterBandH>(
    2382             :                             GDALGetRasterBand(hVRTDS, iBand));
    2383           8 :                     VRTAddSimpleSource(
    2384             :                         hVRTBand, GDALGetRasterBand(poWMSDS, iBand), nSrcXOff,
    2385             :                         nSrcYOff, nSizeX1, nSizeY, nDstXOff, nDstYOff, nSizeX1,
    2386             :                         nSizeY, "NEAR", VRT_NODATA_UNSET);
    2387           8 :                     VRTAddSimpleSource(
    2388             :                         hVRTBand, GDALGetRasterBand(poWMSDS2, iBand), nSrcXOff2,
    2389             :                         nSrcYOff, nSizeX2, nSizeY, nDstXOff2, nDstYOff, nSizeX2,
    2390             :                         nSizeY, "NEAR", VRT_NODATA_UNSET);
    2391             :                 }
    2392             : 
    2393           2 :                 poWMSDS2->Dereference();
    2394             :             }
    2395             :             else
    2396             :             {
    2397         490 :                 for (int iBand = 1; iBand <= nBands; iBand++)
    2398             :                 {
    2399             :                     VRTSourcedRasterBandH hVRTBand =
    2400         392 :                         reinterpret_cast<VRTSourcedRasterBandH>(
    2401             :                             GDALGetRasterBand(hVRTDS, iBand));
    2402         392 :                     VRTAddSimpleSource(
    2403             :                         hVRTBand, GDALGetRasterBand(poWMSDS, iBand), nSrcXOff,
    2404             :                         nSrcYOff, nSizeX, nSizeY, nDstXOff, nDstYOff, nSizeX,
    2405             :                         nSizeY, "NEAR", VRT_NODATA_UNSET);
    2406             :                 }
    2407             :             }
    2408             : 
    2409         100 :             poWMSDS->Dereference();
    2410             : 
    2411         100 :             poDS->apoDatasets.push_back(GDALDataset::FromHandle(hVRTDS));
    2412             :         }
    2413             : 
    2414          47 :         if (poDS->apoDatasets.empty())
    2415             :         {
    2416           0 :             CPLError(CE_Failure, CPLE_AppDefined, "No zoom level found");
    2417           0 :             CPLDestroyXMLNode(psXML);
    2418           0 :             delete poDS;
    2419           0 :             return nullptr;
    2420             :         }
    2421             : 
    2422          47 :         poDS->SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
    2423         235 :         for (int i = 0; i < nBands; i++)
    2424         188 :             poDS->SetBand(i + 1, new WMTSBand(poDS, i + 1, eDataType));
    2425             : 
    2426          47 :         poDS->osXML = "<GDAL_WMTS>\n";
    2427          47 :         poDS->osXML += "  <GetCapabilitiesUrl>" +
    2428         141 :                        WMTSEscapeXML(osGetCapabilitiesURL) +
    2429          47 :                        "</GetCapabilitiesUrl>\n";
    2430          47 :         if (!osSelectLayer.empty())
    2431             :             poDS->osXML +=
    2432          33 :                 "  <Layer>" + WMTSEscapeXML(osSelectLayer) + "</Layer>\n";
    2433          47 :         if (!osSelectStyle.empty())
    2434             :             poDS->osXML +=
    2435          33 :                 "  <Style>" + WMTSEscapeXML(osSelectStyle) + "</Style>\n";
    2436          47 :         if (!osSelectTMS.empty())
    2437          94 :             poDS->osXML += "  <TileMatrixSet>" + WMTSEscapeXML(osSelectTMS) +
    2438          47 :                            "</TileMatrixSet>\n";
    2439          47 :         if (!osMaxTileMatrixIdentifier.empty())
    2440           3 :             poDS->osXML += "  <TileMatrix>" +
    2441           9 :                            WMTSEscapeXML(osMaxTileMatrixIdentifier) +
    2442           3 :                            "</TileMatrix>\n";
    2443          47 :         if (nUserMaxZoomLevel >= 0)
    2444           0 :             poDS->osXML += "  <ZoomLevel>" +
    2445           8 :                            CPLString().Printf("%d", nUserMaxZoomLevel) +
    2446           4 :                            "</ZoomLevel>\n";
    2447          47 :         if (nCountTileFormat > 1 && !osSelectTileFormat.empty())
    2448           2 :             poDS->osXML += "  <Format>" + WMTSEscapeXML(osSelectTileFormat) +
    2449           1 :                            "</Format>\n";
    2450          47 :         if (nCountInfoFormat > 1 && !osSelectInfoFormat.empty())
    2451           0 :             poDS->osXML += "  <InfoFormat>" +
    2452           0 :                            WMTSEscapeXML(osSelectInfoFormat) +
    2453           0 :                            "</InfoFormat>\n";
    2454          47 :         poDS->osXML += "  <DataWindow>\n";
    2455             :         poDS->osXML +=
    2456          47 :             CPLSPrintf("    <UpperLeftX>%.16g</UpperLeftX>\n", poDS->m_gt[0]);
    2457             :         poDS->osXML +=
    2458          47 :             CPLSPrintf("    <UpperLeftY>%.16g</UpperLeftY>\n", poDS->m_gt[3]);
    2459             :         poDS->osXML +=
    2460             :             CPLSPrintf("    <LowerRightX>%.16g</LowerRightX>\n",
    2461          47 :                        poDS->m_gt[0] + poDS->m_gt[1] * poDS->nRasterXSize);
    2462             :         poDS->osXML +=
    2463             :             CPLSPrintf("    <LowerRightY>%.16g</LowerRightY>\n",
    2464          47 :                        poDS->m_gt[3] + poDS->m_gt[5] * poDS->nRasterYSize);
    2465          47 :         poDS->osXML += "  </DataWindow>\n";
    2466          47 :         if (bExtendBeyondDateLine)
    2467             :             poDS->osXML +=
    2468           1 :                 "  <ExtendBeyondDateLine>true</ExtendBeyondDateLine>\n";
    2469          47 :         poDS->osXML += CPLSPrintf("  <BandsCount>%d</BandsCount>\n", nBands);
    2470             :         poDS->osXML += CPLSPrintf("  <DataType>%s</DataType>\n",
    2471          47 :                                   GDALGetDataTypeName(eDataType));
    2472          47 :         poDS->osXML += "  <Cache />\n";
    2473          47 :         poDS->osXML += "  <UnsafeSSL>true</UnsafeSSL>\n";
    2474          47 :         poDS->osXML += "  <ZeroBlockHttpCodes>204,404</ZeroBlockHttpCodes>\n";
    2475             :         poDS->osXML +=
    2476          47 :             "  <ZeroBlockOnServerException>true</ZeroBlockOnServerException>\n";
    2477          47 :         poDS->osXML += "</GDAL_WMTS>\n";
    2478             :     }
    2479             : 
    2480          47 :     CPLDestroyXMLNode(psXML);
    2481             : 
    2482          47 :     poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
    2483          47 :     return poDS;
    2484             : }
    2485             : 
    2486             : /************************************************************************/
    2487             : /*                             CreateCopy()                             */
    2488             : /************************************************************************/
    2489             : 
    2490          21 : GDALDataset *WMTSDataset::CreateCopy(const char *pszFilename,
    2491             :                                      GDALDataset *poSrcDS,
    2492             :                                      CPL_UNUSED int bStrict,
    2493             :                                      CPL_UNUSED char **papszOptions,
    2494             :                                      CPL_UNUSED GDALProgressFunc pfnProgress,
    2495             :                                      CPL_UNUSED void *pProgressData)
    2496             : {
    2497          42 :     if (poSrcDS->GetDriver() == nullptr ||
    2498          21 :         poSrcDS->GetDriver() != GDALGetDriverByName("WMTS"))
    2499             :     {
    2500          19 :         CPLError(CE_Failure, CPLE_NotSupported,
    2501             :                  "Source dataset must be a WMTS dataset");
    2502          19 :         return nullptr;
    2503             :     }
    2504             : 
    2505           2 :     const char *pszXML = poSrcDS->GetMetadataItem("XML", "WMTS");
    2506           2 :     if (pszXML == nullptr)
    2507             :     {
    2508           0 :         CPLError(CE_Failure, CPLE_AppDefined,
    2509             :                  "Cannot get XML definition of source WMTS dataset");
    2510           0 :         return nullptr;
    2511             :     }
    2512             : 
    2513           2 :     VSILFILE *fp = VSIFOpenL(pszFilename, "wb");
    2514           2 :     if (fp == nullptr)
    2515           0 :         return nullptr;
    2516             : 
    2517           2 :     VSIFWriteL(pszXML, 1, strlen(pszXML), fp);
    2518           2 :     VSIFCloseL(fp);
    2519             : 
    2520           4 :     GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
    2521           2 :     return Open(&oOpenInfo);
    2522             : }
    2523             : 
    2524             : /************************************************************************/
    2525             : /*                       GDALRegister_WMTS()                            */
    2526             : /************************************************************************/
    2527             : 
    2528        2038 : void GDALRegister_WMTS()
    2529             : 
    2530             : {
    2531        2038 :     if (!GDAL_CHECK_VERSION("WMTS driver"))
    2532           0 :         return;
    2533             : 
    2534        2038 :     if (GDALGetDriverByName(DRIVER_NAME) != nullptr)
    2535         283 :         return;
    2536             : 
    2537        1755 :     GDALDriver *poDriver = new GDALDriver();
    2538        1755 :     WMTSDriverSetCommonMetadata(poDriver);
    2539             : 
    2540        1755 :     poDriver->pfnOpen = WMTSDataset::Open;
    2541        1755 :     poDriver->pfnCreateCopy = WMTSDataset::CreateCopy;
    2542             : 
    2543        1755 :     GetGDALDriverManager()->RegisterDriver(poDriver);
    2544             : }

Generated by: LCOV version 1.14