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