Coverage Report

Created: 2026-09-26 08:22

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