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