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