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