Coverage Report

Created: 2025-06-09 08:44

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