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