LCOV - code coverage report
Current view: top level - frmts/wmts - wmtsdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 1054 1168 90.2 %
Date: 2018-04-20 Functions: 40 42 95.2 %

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

Generated by: LCOV version 1.10