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

Generated by: LCOV version 1.14