Coverage Report

Created: 2026-03-30 09:00

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