Coverage Report

Created: 2025-06-13 06:18

/src/gdal/gcore/tilematrixset.cpp
Line
Count
Source (jump to first uncovered line)
1
/******************************************************************************
2
 *
3
 * Project:  GDAL
4
 * Purpose:  Class to handle TileMatrixSet
5
 * Author:   Even Rouault, <even dot rouault at spatialys.com>
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2020, Even Rouault <even dot rouault at spatialys.com>
9
 *
10
 * SPDX-License-Identifier: MIT
11
 ****************************************************************************/
12
13
#include "cpl_json.h"
14
#include "gdal_priv.h"
15
#include "ogr_spatialref.h"
16
17
#include <algorithm>
18
#include <cmath>
19
#include <cfloat>
20
#include <limits>
21
22
#include "tilematrixset.hpp"
23
24
//! @cond Doxygen_Suppress
25
26
namespace gdal
27
{
28
29
/************************************************************************/
30
/*                   listPredefinedTileMatrixSets()                     */
31
/************************************************************************/
32
33
std::vector<std::string> TileMatrixSet::listPredefinedTileMatrixSets()
34
0
{
35
0
    std::vector<std::string> l{"GoogleMapsCompatible", "WorldCRS84Quad",
36
0
                               "WorldMercatorWGS84Quad", "GoogleCRS84Quad",
37
0
                               "PseudoTMS_GlobalMercator"};
38
0
    const char *pszSomeFile = CPLFindFile("gdal", "tms_NZTM2000.json");
39
0
    if (pszSomeFile)
40
0
    {
41
0
        std::set<std::string> set;
42
0
        CPLStringList aosList(
43
0
            VSIReadDir(CPLGetDirnameSafe(pszSomeFile).c_str()));
44
0
        for (int i = 0; i < aosList.size(); i++)
45
0
        {
46
0
            const size_t nLen = strlen(aosList[i]);
47
0
            if (nLen > strlen("tms_") + strlen(".json") &&
48
0
                STARTS_WITH(aosList[i], "tms_") &&
49
0
                EQUAL(aosList[i] + nLen - strlen(".json"), ".json"))
50
0
            {
51
0
                std::string id(aosList[i] + strlen("tms_"),
52
0
                               nLen - (strlen("tms_") + strlen(".json")));
53
0
                set.insert(std::move(id));
54
0
            }
55
0
        }
56
0
        for (const std::string &id : set)
57
0
            l.push_back(id);
58
0
    }
59
0
    return l;
60
0
}
61
62
/************************************************************************/
63
/*                              parse()                                 */
64
/************************************************************************/
65
66
std::unique_ptr<TileMatrixSet> TileMatrixSet::parse(const char *fileOrDef)
67
0
{
68
0
    CPLJSONDocument oDoc;
69
0
    std::unique_ptr<TileMatrixSet> poTMS(new TileMatrixSet());
70
71
0
    constexpr double HALF_CIRCUMFERENCE = 6378137 * M_PI;
72
73
0
    if (EQUAL(fileOrDef, "GoogleMapsCompatible") ||
74
0
        EQUAL(fileOrDef, "WebMercatorQuad") ||
75
0
        EQUAL(
76
0
            fileOrDef,
77
0
            "http://www.opengis.net/def/tilematrixset/OGC/1.0/WebMercatorQuad"))
78
0
    {
79
        /* See http://portal.opengeospatial.org/files/?artifact_id=35326
80
         * (WMTS 1.0), Annex E.4 */
81
        // or https://docs.ogc.org/is/17-083r4/17-083r4.html#toc49
82
0
        poTMS->mTitle = "GoogleMapsCompatible";
83
0
        poTMS->mIdentifier = "GoogleMapsCompatible";
84
0
        poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
85
0
        poTMS->mBbox.mCrs = poTMS->mCrs;
86
0
        poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
87
0
        poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
88
0
        poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
89
0
        poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
90
0
        poTMS->mWellKnownScaleSet =
91
0
            "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible";
92
0
        for (int i = 0; i <= 30; i++)
93
0
        {
94
0
            TileMatrix tm;
95
0
            tm.mId = CPLSPrintf("%d", i);
96
0
            tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
97
0
            tm.mResY = tm.mResX;
98
0
            tm.mScaleDenominator = tm.mResX / 0.28e-3;
99
0
            tm.mTopLeftX = -HALF_CIRCUMFERENCE;
100
0
            tm.mTopLeftY = HALF_CIRCUMFERENCE;
101
0
            tm.mTileWidth = 256;
102
0
            tm.mTileHeight = 256;
103
0
            tm.mMatrixWidth = 1 << i;
104
0
            tm.mMatrixHeight = 1 << i;
105
0
            poTMS->mTileMatrixList.emplace_back(std::move(tm));
106
0
        }
107
0
        return poTMS;
108
0
    }
109
110
0
    if (EQUAL(fileOrDef, "WorldMercatorWGS84Quad") ||
111
0
        EQUAL(fileOrDef, "http://www.opengis.net/def/tilematrixset/OGC/1.0/"
112
0
                         "WorldMercatorWGS84Quad"))
113
0
    {
114
        // See https://docs.ogc.org/is/17-083r4/17-083r4.html#toc51
115
0
        poTMS->mTitle = "WorldMercatorWGS84Quad";
116
0
        poTMS->mIdentifier = "WorldMercatorWGS84Quad";
117
0
        poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3395";
118
0
        poTMS->mBbox.mCrs = poTMS->mCrs;
119
0
        poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
120
0
        poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
121
0
        poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
122
0
        poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
123
0
        poTMS->mWellKnownScaleSet =
124
0
            "http://www.opengis.net/def/wkss/OGC/1.0/WorldMercatorWGS84Quad";
125
0
        for (int i = 0; i <= 30; i++)
126
0
        {
127
0
            TileMatrix tm;
128
0
            tm.mId = CPLSPrintf("%d", i);
129
0
            tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
130
0
            tm.mResY = tm.mResX;
131
0
            tm.mScaleDenominator = tm.mResX / 0.28e-3;
132
0
            tm.mTopLeftX = -HALF_CIRCUMFERENCE;
133
0
            tm.mTopLeftY = HALF_CIRCUMFERENCE;
134
0
            tm.mTileWidth = 256;
135
0
            tm.mTileHeight = 256;
136
0
            tm.mMatrixWidth = 1 << i;
137
0
            tm.mMatrixHeight = 1 << i;
138
0
            poTMS->mTileMatrixList.emplace_back(std::move(tm));
139
0
        }
140
0
        return poTMS;
141
0
    }
142
143
0
    if (EQUAL(fileOrDef, "PseudoTMS_GlobalMercator"))
144
0
    {
145
        /* See global-mercator at
146
           http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
147
0
        poTMS->mTitle = "PseudoTMS_GlobalMercator";
148
0
        poTMS->mIdentifier = "PseudoTMS_GlobalMercator";
149
0
        poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
150
0
        poTMS->mBbox.mCrs = poTMS->mCrs;
151
0
        poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
152
0
        poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
153
0
        poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
154
0
        poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
155
0
        for (int i = 0; i <= 29; i++)
156
0
        {
157
0
            TileMatrix tm;
158
0
            tm.mId = CPLSPrintf("%d", i);
159
0
            tm.mResX = HALF_CIRCUMFERENCE / 256 / (1 << i);
160
0
            tm.mResY = tm.mResX;
161
0
            tm.mScaleDenominator = tm.mResX / 0.28e-3;
162
0
            tm.mTopLeftX = -HALF_CIRCUMFERENCE;
163
0
            tm.mTopLeftY = HALF_CIRCUMFERENCE;
164
0
            tm.mTileWidth = 256;
165
0
            tm.mTileHeight = 256;
166
0
            tm.mMatrixWidth = 2 * (1 << i);
167
0
            tm.mMatrixHeight = 2 * (1 << i);
168
0
            poTMS->mTileMatrixList.emplace_back(std::move(tm));
169
0
        }
170
0
        return poTMS;
171
0
    }
172
173
0
    if (EQUAL(fileOrDef, "InspireCRS84Quad") ||
174
0
        EQUAL(fileOrDef, "PseudoTMS_GlobalGeodetic") ||
175
0
        EQUAL(fileOrDef, "WorldCRS84Quad") ||
176
0
        EQUAL(
177
0
            fileOrDef,
178
0
            "http://www.opengis.net/def/tilematrixset/OGC/1.0/WorldCRS84Quad"))
179
0
    {
180
        /* See InspireCRS84Quad at
181
         * http://inspire.ec.europa.eu/documents/Network_Services/TechnicalGuidance_ViewServices_v3.0.pdf
182
         */
183
        /* This is exactly the same as PseudoTMS_GlobalGeodetic */
184
        /* See global-geodetic at
185
         * http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
186
        // See also http://docs.opengeospatial.org/is/17-083r2/17-083r2.html#76
187
0
        poTMS->mTitle = "WorldCRS84Quad";
188
0
        poTMS->mIdentifier = "WorldCRS84Quad";
189
0
        poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
190
0
        poTMS->mBbox.mCrs = poTMS->mCrs;
191
0
        poTMS->mBbox.mLowerCornerX = -180;
192
0
        poTMS->mBbox.mLowerCornerY = -90;
193
0
        poTMS->mBbox.mUpperCornerX = 180;
194
0
        poTMS->mBbox.mUpperCornerY = 90;
195
0
        poTMS->mWellKnownScaleSet =
196
0
            "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad";
197
        // Limit to zoom level 29, because at that zoom level nMatrixWidth = 2 * (1 << 29) = 1073741824
198
        // and at 30 it would overflow int32.
199
0
        for (int i = 0; i <= 29; i++)
200
0
        {
201
0
            TileMatrix tm;
202
0
            tm.mId = CPLSPrintf("%d", i);
203
0
            tm.mResX = 180. / 256 / (1 << i);
204
0
            tm.mResY = tm.mResX;
205
0
            tm.mScaleDenominator =
206
0
                tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
207
0
            tm.mTopLeftX = -180;
208
0
            tm.mTopLeftY = 90;
209
0
            tm.mTileWidth = 256;
210
0
            tm.mTileHeight = 256;
211
0
            tm.mMatrixWidth = 2 * (1 << i);
212
0
            tm.mMatrixHeight = 1 << i;
213
0
            poTMS->mTileMatrixList.emplace_back(std::move(tm));
214
0
        }
215
0
        return poTMS;
216
0
    }
217
218
0
    if (EQUAL(fileOrDef, "GoogleCRS84Quad") ||
219
0
        EQUAL(fileOrDef,
220
0
              "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad"))
221
0
    {
222
        /* See http://portal.opengeospatial.org/files/?artifact_id=35326 (WMTS 1.0),
223
               Annex E.3 */
224
0
        poTMS->mTitle = "GoogleCRS84Quad";
225
0
        poTMS->mIdentifier = "GoogleCRS84Quad";
226
0
        poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
227
0
        poTMS->mBbox.mCrs = poTMS->mCrs;
228
0
        poTMS->mBbox.mLowerCornerX = -180;
229
0
        poTMS->mBbox.mLowerCornerY = -90;
230
0
        poTMS->mBbox.mUpperCornerX = 180;
231
0
        poTMS->mBbox.mUpperCornerY = 90;
232
0
        poTMS->mWellKnownScaleSet =
233
0
            "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad";
234
0
        for (int i = 0; i <= 30; i++)
235
0
        {
236
0
            TileMatrix tm;
237
0
            tm.mId = CPLSPrintf("%d", i);
238
0
            tm.mResX = 360. / 256 / (1 << i);
239
0
            tm.mResY = tm.mResX;
240
0
            tm.mScaleDenominator =
241
0
                tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
242
0
            tm.mTopLeftX = -180;
243
0
            tm.mTopLeftY = 180;
244
0
            tm.mTileWidth = 256;
245
0
            tm.mTileHeight = 256;
246
0
            tm.mMatrixWidth = 1 << i;
247
0
            tm.mMatrixHeight = 1 << i;
248
0
            poTMS->mTileMatrixList.emplace_back(std::move(tm));
249
0
        }
250
0
        return poTMS;
251
0
    }
252
253
0
    bool loadOk = false;
254
0
    if (  // TMS 2.0 spec
255
0
        (strstr(fileOrDef, "\"crs\"") &&
256
0
         strstr(fileOrDef, "\"tileMatrices\"")) ||
257
        // TMS 1.0 spec
258
0
        (strstr(fileOrDef, "\"type\"") &&
259
0
         strstr(fileOrDef, "\"TileMatrixSetType\"")) ||
260
0
        (strstr(fileOrDef, "\"identifier\"") &&
261
0
         strstr(fileOrDef, "\"boundingBox\"") &&
262
0
         strstr(fileOrDef, "\"tileMatrix\"")))
263
0
    {
264
0
        loadOk = oDoc.LoadMemory(fileOrDef);
265
0
    }
266
0
    else if (STARTS_WITH_CI(fileOrDef, "http://") ||
267
0
             STARTS_WITH_CI(fileOrDef, "https://"))
268
0
    {
269
0
        const char *const apszOptions[] = {"MAX_FILE_SIZE=1000000", nullptr};
270
0
        loadOk = oDoc.LoadUrl(fileOrDef, apszOptions);
271
0
    }
272
0
    else
273
0
    {
274
0
        VSIStatBufL sStat;
275
0
        if (VSIStatL(fileOrDef, &sStat) == 0)
276
0
        {
277
0
            loadOk = oDoc.Load(fileOrDef);
278
0
        }
279
0
        else
280
0
        {
281
0
            const char *pszFilename = CPLFindFile(
282
0
                "gdal", (std::string("tms_") + fileOrDef + ".json").c_str());
283
0
            if (pszFilename)
284
0
            {
285
0
                loadOk = oDoc.Load(pszFilename);
286
0
            }
287
0
            else
288
0
            {
289
0
                CPLError(CE_Failure, CPLE_AppDefined,
290
0
                         "Invalid tiling matrix set name");
291
0
            }
292
0
        }
293
0
    }
294
0
    if (!loadOk)
295
0
    {
296
0
        return nullptr;
297
0
    }
298
299
0
    auto oRoot = oDoc.GetRoot();
300
0
    const bool bIsTMSv2 =
301
0
        oRoot.GetObj("crs").IsValid() && oRoot.GetObj("tileMatrices").IsValid();
302
303
0
    if (!bIsTMSv2 && oRoot.GetString("type") != "TileMatrixSetType" &&
304
0
        !oRoot.GetObj("tileMatrix").IsValid())
305
0
    {
306
0
        CPLError(CE_Failure, CPLE_AppDefined,
307
0
                 "Expected type = TileMatrixSetType");
308
0
        return nullptr;
309
0
    }
310
311
0
    const auto GetCRS = [](const CPLJSONObject &j)
312
0
    {
313
0
        if (j.IsValid())
314
0
        {
315
0
            if (j.GetType() == CPLJSONObject::Type::String)
316
0
                return j.ToString();
317
318
0
            else if (j.GetType() == CPLJSONObject::Type::Object)
319
0
            {
320
0
                std::string osURI = j.GetString("uri");
321
0
                if (!osURI.empty())
322
0
                    return osURI;
323
324
                // Quite a bit of confusion around wkt.
325
                // See https://github.com/opengeospatial/ogcapi-tiles/issues/170
326
0
                const auto jWKT = j.GetObj("wkt");
327
0
                if (jWKT.GetType() == CPLJSONObject::Type::String)
328
0
                {
329
0
                    std::string osWKT = jWKT.ToString();
330
0
                    if (!osWKT.empty())
331
0
                        return osWKT;
332
0
                }
333
0
                else if (jWKT.GetType() == CPLJSONObject::Type::Object)
334
0
                {
335
0
                    std::string osWKT = jWKT.ToString();
336
0
                    if (!osWKT.empty())
337
0
                        return osWKT;
338
0
                }
339
0
            }
340
0
        }
341
0
        return std::string();
342
0
    };
343
344
0
    poTMS->mIdentifier = oRoot.GetString(bIsTMSv2 ? "id" : "identifier");
345
0
    poTMS->mTitle = oRoot.GetString("title");
346
0
    poTMS->mAbstract = oRoot.GetString(bIsTMSv2 ? "description" : "abstract");
347
0
    const auto oBbox = oRoot.GetObj("boundingBox");
348
0
    if (oBbox.IsValid())
349
0
    {
350
0
        poTMS->mBbox.mCrs = GetCRS(oBbox.GetObj("crs"));
351
0
        const auto oLowerCorner = oBbox.GetArray("lowerCorner");
352
0
        if (oLowerCorner.IsValid() && oLowerCorner.Size() == 2)
353
0
        {
354
0
            poTMS->mBbox.mLowerCornerX = oLowerCorner[0].ToDouble(NaN);
355
0
            poTMS->mBbox.mLowerCornerY = oLowerCorner[1].ToDouble(NaN);
356
0
        }
357
0
        const auto oUpperCorner = oBbox.GetArray("upperCorner");
358
0
        if (oUpperCorner.IsValid() && oUpperCorner.Size() == 2)
359
0
        {
360
0
            poTMS->mBbox.mUpperCornerX = oUpperCorner[0].ToDouble(NaN);
361
0
            poTMS->mBbox.mUpperCornerY = oUpperCorner[1].ToDouble(NaN);
362
0
        }
363
0
    }
364
0
    poTMS->mCrs = GetCRS(oRoot.GetObj(bIsTMSv2 ? "crs" : "supportedCRS"));
365
0
    poTMS->mWellKnownScaleSet = oRoot.GetString("wellKnownScaleSet");
366
367
0
    OGRSpatialReference oCrs;
368
0
    if (oCrs.SetFromUserInput(
369
0
            poTMS->mCrs.c_str(),
370
0
            OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS) !=
371
0
        OGRERR_NONE)
372
0
    {
373
0
        CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse CRS %s",
374
0
                 poTMS->mCrs.c_str());
375
0
        return nullptr;
376
0
    }
377
0
    double dfMetersPerUnit = 1.0;
378
0
    if (oCrs.IsProjected())
379
0
    {
380
0
        dfMetersPerUnit = oCrs.GetLinearUnits();
381
0
    }
382
0
    else if (oCrs.IsGeographic())
383
0
    {
384
0
        dfMetersPerUnit = oCrs.GetSemiMajor() * M_PI / 180;
385
0
    }
386
387
0
    const auto oTileMatrices =
388
0
        oRoot.GetArray(bIsTMSv2 ? "tileMatrices" : "tileMatrix");
389
0
    if (oTileMatrices.IsValid())
390
0
    {
391
0
        double dfLastScaleDenominator = std::numeric_limits<double>::max();
392
0
        for (const auto &oTM : oTileMatrices)
393
0
        {
394
0
            TileMatrix tm;
395
0
            tm.mId = oTM.GetString(bIsTMSv2 ? "id" : "identifier");
396
0
            tm.mScaleDenominator = oTM.GetDouble("scaleDenominator");
397
0
            if (tm.mScaleDenominator >= dfLastScaleDenominator ||
398
0
                tm.mScaleDenominator <= 0)
399
0
            {
400
0
                CPLError(CE_Failure, CPLE_AppDefined,
401
0
                         "Invalid scale denominator or non-decreasing series "
402
0
                         "of scale denominators");
403
0
                return nullptr;
404
0
            }
405
0
            dfLastScaleDenominator = tm.mScaleDenominator;
406
            // See note g of Table 2 of
407
            // http://docs.opengeospatial.org/is/17-083r2/17-083r2.html
408
0
            tm.mResX = tm.mScaleDenominator * 0.28e-3 / dfMetersPerUnit;
409
0
            tm.mResY = tm.mResX;
410
0
            if (bIsTMSv2)
411
0
            {
412
0
                const auto osCornerOfOrigin = oTM.GetString("cornerOfOrigin");
413
0
                if (!osCornerOfOrigin.empty() && osCornerOfOrigin != "topLeft")
414
0
                {
415
0
                    CPLError(CE_Warning, CPLE_AppDefined,
416
0
                             "cornerOfOrigin = %s not supported",
417
0
                             osCornerOfOrigin.c_str());
418
0
                }
419
0
            }
420
0
            const auto oTopLeftCorner =
421
0
                oTM.GetArray(bIsTMSv2 ? "pointOfOrigin" : "topLeftCorner");
422
0
            if (oTopLeftCorner.IsValid() && oTopLeftCorner.Size() == 2)
423
0
            {
424
0
                tm.mTopLeftX = oTopLeftCorner[0].ToDouble(NaN);
425
0
                tm.mTopLeftY = oTopLeftCorner[1].ToDouble(NaN);
426
0
            }
427
0
            tm.mTileWidth = oTM.GetInteger("tileWidth");
428
0
            if (tm.mTileWidth <= 0)
429
0
            {
430
0
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileWidth: %d",
431
0
                         tm.mTileWidth);
432
0
                return nullptr;
433
0
            }
434
0
            tm.mTileHeight = oTM.GetInteger("tileHeight");
435
0
            if (tm.mTileHeight <= 0)
436
0
            {
437
0
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileHeight: %d",
438
0
                         tm.mTileHeight);
439
0
                return nullptr;
440
0
            }
441
0
            if (tm.mTileWidth > INT_MAX / tm.mTileHeight)
442
0
            {
443
0
                CPLError(CE_Failure, CPLE_AppDefined,
444
0
                         "tileWidth(%d) x tileHeight(%d) larger than "
445
0
                         "INT_MAX",
446
0
                         tm.mTileWidth, tm.mTileHeight);
447
0
                return nullptr;
448
0
            }
449
0
            tm.mMatrixWidth = oTM.GetInteger("matrixWidth");
450
0
            if (tm.mMatrixWidth <= 0)
451
0
            {
452
0
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid matrixWidth: %d",
453
0
                         tm.mMatrixWidth);
454
0
                return nullptr;
455
0
            }
456
0
            tm.mMatrixHeight = oTM.GetInteger("matrixHeight");
457
0
            if (tm.mMatrixHeight <= 0)
458
0
            {
459
0
                CPLError(CE_Failure, CPLE_AppDefined,
460
0
                         "Invalid matrixHeight: %d", tm.mMatrixHeight);
461
0
                return nullptr;
462
0
            }
463
464
0
            const auto oVariableMatrixWidths = oTM.GetArray(
465
0
                bIsTMSv2 ? "variableMatrixWidths" : "variableMatrixWidth");
466
0
            if (oVariableMatrixWidths.IsValid())
467
0
            {
468
0
                for (const auto &oVMW : oVariableMatrixWidths)
469
0
                {
470
0
                    TileMatrix::VariableMatrixWidth vmw;
471
0
                    vmw.mCoalesce = oVMW.GetInteger("coalesce");
472
0
                    vmw.mMinTileRow = oVMW.GetInteger("minTileRow");
473
0
                    vmw.mMaxTileRow = oVMW.GetInteger("maxTileRow");
474
0
                    tm.mVariableMatrixWidthList.emplace_back(std::move(vmw));
475
0
                }
476
0
            }
477
478
0
            poTMS->mTileMatrixList.emplace_back(std::move(tm));
479
0
        }
480
0
    }
481
0
    if (poTMS->mTileMatrixList.empty())
482
0
    {
483
0
        CPLError(CE_Failure, CPLE_AppDefined, "No tileMatrix defined");
484
0
        return nullptr;
485
0
    }
486
487
0
    return poTMS;
488
0
}
489
490
/************************************************************************/
491
/*                       haveAllLevelsSameTopLeft()                     */
492
/************************************************************************/
493
494
bool TileMatrixSet::haveAllLevelsSameTopLeft() const
495
0
{
496
0
    for (const auto &oTM : mTileMatrixList)
497
0
    {
498
0
        if (oTM.mTopLeftX != mTileMatrixList[0].mTopLeftX ||
499
0
            oTM.mTopLeftY != mTileMatrixList[0].mTopLeftY)
500
0
        {
501
0
            return false;
502
0
        }
503
0
    }
504
0
    return true;
505
0
}
506
507
/************************************************************************/
508
/*                      haveAllLevelsSameTileSize()                     */
509
/************************************************************************/
510
511
bool TileMatrixSet::haveAllLevelsSameTileSize() const
512
0
{
513
0
    for (const auto &oTM : mTileMatrixList)
514
0
    {
515
0
        if (oTM.mTileWidth != mTileMatrixList[0].mTileWidth ||
516
0
            oTM.mTileHeight != mTileMatrixList[0].mTileHeight)
517
0
        {
518
0
            return false;
519
0
        }
520
0
    }
521
0
    return true;
522
0
}
523
524
/************************************************************************/
525
/*                    hasOnlyPowerOfTwoVaryingScales()                  */
526
/************************************************************************/
527
528
bool TileMatrixSet::hasOnlyPowerOfTwoVaryingScales() const
529
0
{
530
0
    for (size_t i = 1; i < mTileMatrixList.size(); i++)
531
0
    {
532
0
        if (mTileMatrixList[i].mScaleDenominator == 0 ||
533
0
            std::fabs(mTileMatrixList[i - 1].mScaleDenominator /
534
0
                          mTileMatrixList[i].mScaleDenominator -
535
0
                      2) > 1e-10)
536
0
        {
537
0
            return false;
538
0
        }
539
0
    }
540
0
    return true;
541
0
}
542
543
/************************************************************************/
544
/*                        hasVariableMatrixWidth()                      */
545
/************************************************************************/
546
547
bool TileMatrixSet::hasVariableMatrixWidth() const
548
0
{
549
0
    for (const auto &oTM : mTileMatrixList)
550
0
    {
551
0
        if (!oTM.mVariableMatrixWidthList.empty())
552
0
        {
553
0
            return true;
554
0
        }
555
0
    }
556
0
    return false;
557
0
}
558
559
/************************************************************************/
560
/*                            createRaster()                            */
561
/************************************************************************/
562
563
/* static */
564
std::unique_ptr<TileMatrixSet>
565
TileMatrixSet::createRaster(int width, int height, int tileSize,
566
                            int zoomLevelCount, double dfTopLeftX,
567
                            double dfTopLeftY, double dfResXFull,
568
                            double dfResYFull, const std::string &crs)
569
0
{
570
0
    CPLAssert(width > 0);
571
0
    CPLAssert(height > 0);
572
0
    CPLAssert(tileSize > 0);
573
0
    CPLAssert(zoomLevelCount > 0);
574
0
    std::unique_ptr<TileMatrixSet> poTMS(new TileMatrixSet());
575
0
    poTMS->mTitle = "raster";
576
0
    poTMS->mIdentifier = "raster";
577
0
    poTMS->mCrs = crs;
578
0
    poTMS->mBbox.mCrs = poTMS->mCrs;
579
0
    poTMS->mBbox.mLowerCornerX = dfTopLeftX;
580
0
    poTMS->mBbox.mLowerCornerY = dfTopLeftY - height * dfResYFull;
581
0
    poTMS->mBbox.mUpperCornerX = dfTopLeftX + width * dfResYFull;
582
0
    poTMS->mBbox.mUpperCornerY = dfTopLeftY;
583
0
    for (int i = 0; i < zoomLevelCount; i++)
584
0
    {
585
0
        TileMatrix tm;
586
0
        tm.mId = CPLSPrintf("%d", i);
587
0
        const int iRev = zoomLevelCount - 1 - i;
588
0
        tm.mResX = dfResXFull * (1 << iRev);
589
0
        tm.mResY = dfResYFull * (1 << iRev);
590
0
        tm.mScaleDenominator = tm.mResX / 0.28e-3;
591
0
        tm.mTopLeftX = poTMS->mBbox.mLowerCornerX;
592
0
        tm.mTopLeftY = poTMS->mBbox.mUpperCornerY;
593
0
        tm.mTileWidth = tileSize;
594
0
        tm.mTileHeight = tileSize;
595
0
        tm.mMatrixWidth = std::max(1, DIV_ROUND_UP(width >> iRev, tileSize));
596
0
        tm.mMatrixHeight = std::max(1, DIV_ROUND_UP(height >> iRev, tileSize));
597
0
        poTMS->mTileMatrixList.emplace_back(std::move(tm));
598
0
    }
599
0
    return poTMS;
600
0
}
601
602
}  // namespace gdal
603
604
//! @endcond