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