Coverage Report

Created: 2026-04-01 06:20

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
0
{
36
0
    std::vector<std::string> l{"GoogleMapsCompatible", "WorldCRS84Quad",
37
0
                               "WorldMercatorWGS84Quad", "GoogleCRS84Quad",
38
0
                               "PseudoTMS_GlobalMercator"};
39
0
    if (includeHidden)
40
0
        l.push_back("GlobalGeodeticOriginLat270");
41
0
    const char *pszSomeFile = CPLFindFile("gdal", "tms_NZTM2000.json");
42
0
    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
0
    return l;
63
0
}
64
65
/************************************************************************/
66
/*                               parse()                                */
67
/************************************************************************/
68
69
std::unique_ptr<TileMatrixSet> TileMatrixSet::parse(const char *fileOrDef)
70
0
{
71
0
    CPLJSONDocument oDoc;
72
0
    std::unique_ptr<TileMatrixSet> poTMS(new TileMatrixSet());
73
74
0
    constexpr double HALF_CIRCUMFERENCE = 6378137 * M_PI;
75
76
0
    if (EQUAL(fileOrDef, "GoogleMapsCompatible") ||
77
0
        EQUAL(fileOrDef, "WebMercatorQuad") ||
78
0
        EQUAL(
79
0
            fileOrDef,
80
0
            "http://www.opengis.net/def/tilematrixset/OGC/1.0/WebMercatorQuad"))
81
0
    {
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
0
        poTMS->mTitle = "GoogleMapsCompatible";
86
0
        poTMS->mIdentifier = "GoogleMapsCompatible";
87
0
        poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
88
0
        poTMS->mBbox.mCrs = poTMS->mCrs;
89
0
        poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
90
0
        poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
91
0
        poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
92
0
        poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
93
0
        poTMS->mWellKnownScaleSet =
94
0
            "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible";
95
0
        for (int i = 0; i <= 30; i++)
96
0
        {
97
0
            TileMatrix tm;
98
0
            tm.mId = CPLSPrintf("%d", i);
99
0
            tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
100
0
            tm.mResY = tm.mResX;
101
0
            tm.mScaleDenominator = tm.mResX / 0.28e-3;
102
0
            tm.mTopLeftX = -HALF_CIRCUMFERENCE;
103
0
            tm.mTopLeftY = HALF_CIRCUMFERENCE;
104
0
            tm.mTileWidth = 256;
105
0
            tm.mTileHeight = 256;
106
0
            tm.mMatrixWidth = 1 << i;
107
0
            tm.mMatrixHeight = 1 << i;
108
0
            poTMS->mTileMatrixList.emplace_back(std::move(tm));
109
0
        }
110
0
        return poTMS;
111
0
    }
112
113
0
    if (EQUAL(fileOrDef, "WorldMercatorWGS84Quad") ||
114
0
        EQUAL(fileOrDef, "http://www.opengis.net/def/tilematrixset/OGC/1.0/"
115
0
                         "WorldMercatorWGS84Quad"))
116
0
    {
117
        // See https://docs.ogc.org/is/17-083r4/17-083r4.html#toc51
118
0
        poTMS->mTitle = "WorldMercatorWGS84Quad";
119
0
        poTMS->mIdentifier = "WorldMercatorWGS84Quad";
120
0
        poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3395";
121
0
        poTMS->mBbox.mCrs = poTMS->mCrs;
122
0
        poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
123
0
        poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
124
0
        poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
125
0
        poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
126
0
        poTMS->mWellKnownScaleSet =
127
0
            "http://www.opengis.net/def/wkss/OGC/1.0/WorldMercatorWGS84Quad";
128
0
        for (int i = 0; i <= 30; i++)
129
0
        {
130
0
            TileMatrix tm;
131
0
            tm.mId = CPLSPrintf("%d", i);
132
0
            tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
133
0
            tm.mResY = tm.mResX;
134
0
            tm.mScaleDenominator = tm.mResX / 0.28e-3;
135
0
            tm.mTopLeftX = -HALF_CIRCUMFERENCE;
136
0
            tm.mTopLeftY = HALF_CIRCUMFERENCE;
137
0
            tm.mTileWidth = 256;
138
0
            tm.mTileHeight = 256;
139
0
            tm.mMatrixWidth = 1 << i;
140
0
            tm.mMatrixHeight = 1 << i;
141
0
            poTMS->mTileMatrixList.emplace_back(std::move(tm));
142
0
        }
143
0
        return poTMS;
144
0
    }
145
146
0
    if (EQUAL(fileOrDef, "PseudoTMS_GlobalMercator"))
147
0
    {
148
        /* See global-mercator at
149
           http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
150
0
        poTMS->mTitle = "PseudoTMS_GlobalMercator";
151
0
        poTMS->mIdentifier = "PseudoTMS_GlobalMercator";
152
0
        poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
153
0
        poTMS->mBbox.mCrs = poTMS->mCrs;
154
0
        poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
155
0
        poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
156
0
        poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
157
0
        poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
158
0
        for (int i = 0; i <= 29; i++)
159
0
        {
160
0
            TileMatrix tm;
161
0
            tm.mId = CPLSPrintf("%d", i);
162
0
            tm.mResX = HALF_CIRCUMFERENCE / 256 / (1 << i);
163
0
            tm.mResY = tm.mResX;
164
0
            tm.mScaleDenominator = tm.mResX / 0.28e-3;
165
0
            tm.mTopLeftX = -HALF_CIRCUMFERENCE;
166
0
            tm.mTopLeftY = HALF_CIRCUMFERENCE;
167
0
            tm.mTileWidth = 256;
168
0
            tm.mTileHeight = 256;
169
0
            tm.mMatrixWidth = 2 * (1 << i);
170
0
            tm.mMatrixHeight = 2 * (1 << i);
171
0
            poTMS->mTileMatrixList.emplace_back(std::move(tm));
172
0
        }
173
0
        return poTMS;
174
0
    }
175
176
0
    if (EQUAL(fileOrDef, "InspireCRS84Quad") ||
177
0
        EQUAL(fileOrDef, "PseudoTMS_GlobalGeodetic") ||
178
0
        EQUAL(fileOrDef, "WorldCRS84Quad") ||
179
0
        EQUAL(
180
0
            fileOrDef,
181
0
            "http://www.opengis.net/def/tilematrixset/OGC/1.0/WorldCRS84Quad"))
182
0
    {
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
0
        poTMS->mTitle = "WorldCRS84Quad";
191
0
        poTMS->mIdentifier = "WorldCRS84Quad";
192
0
        poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
193
0
        poTMS->mBbox.mCrs = poTMS->mCrs;
194
0
        poTMS->mBbox.mLowerCornerX = -180;
195
0
        poTMS->mBbox.mLowerCornerY = -90;
196
0
        poTMS->mBbox.mUpperCornerX = 180;
197
0
        poTMS->mBbox.mUpperCornerY = 90;
198
0
        poTMS->mWellKnownScaleSet =
199
0
            "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
0
        for (int i = 0; i <= 29; i++)
203
0
        {
204
0
            TileMatrix tm;
205
0
            tm.mId = CPLSPrintf("%d", i);
206
0
            tm.mResX = 180. / 256 / (1 << i);
207
0
            tm.mResY = tm.mResX;
208
0
            tm.mScaleDenominator =
209
0
                tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
210
0
            tm.mTopLeftX = -180;
211
0
            tm.mTopLeftY = 90;
212
0
            tm.mTileWidth = 256;
213
0
            tm.mTileHeight = 256;
214
0
            tm.mMatrixWidth = 2 * (1 << i);
215
0
            tm.mMatrixHeight = 1 << i;
216
0
            poTMS->mTileMatrixList.emplace_back(std::move(tm));
217
0
        }
218
0
        return poTMS;
219
0
    }
220
221
0
    if (EQUAL(fileOrDef, "GoogleCRS84Quad") ||
222
0
        EQUAL(fileOrDef,
223
0
              "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad"))
224
0
    {
225
        /* See http://portal.opengeospatial.org/files/?artifact_id=35326 (WMTS 1.0),
226
               Annex E.3 */
227
0
        poTMS->mTitle = "GoogleCRS84Quad";
228
0
        poTMS->mIdentifier = "GoogleCRS84Quad";
229
0
        poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
230
0
        poTMS->mBbox.mCrs = poTMS->mCrs;
231
0
        poTMS->mBbox.mLowerCornerX = -180;
232
0
        poTMS->mBbox.mLowerCornerY = -90;
233
0
        poTMS->mBbox.mUpperCornerX = 180;
234
0
        poTMS->mBbox.mUpperCornerY = 90;
235
0
        poTMS->mWellKnownScaleSet =
236
0
            "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad";
237
0
        for (int i = 0; i <= 30; i++)
238
0
        {
239
0
            TileMatrix tm;
240
0
            tm.mId = CPLSPrintf("%d", i);
241
0
            tm.mResX = 360. / 256 / (1 << i);
242
0
            tm.mResY = tm.mResX;
243
0
            tm.mScaleDenominator =
244
0
                tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
245
0
            tm.mTopLeftX = -180;
246
0
            tm.mTopLeftY = 180;
247
0
            tm.mTileWidth = 256;
248
0
            tm.mTileHeight = 256;
249
0
            tm.mMatrixWidth = 1 << i;
250
0
            tm.mMatrixHeight = 1 << i;
251
0
            poTMS->mTileMatrixList.emplace_back(std::move(tm));
252
0
        }
253
0
        return poTMS;
254
0
    }
255
256
0
    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
0
    bool loadOk = false;
287
0
    if (  // TMS 2.0 spec
288
0
        (strstr(fileOrDef, "\"crs\"") &&
289
0
         strstr(fileOrDef, "\"tileMatrices\"")) ||
290
        // TMS 1.0 spec
291
0
        (strstr(fileOrDef, "\"type\"") &&
292
0
         strstr(fileOrDef, "\"TileMatrixSetType\"")) ||
293
0
        (strstr(fileOrDef, "\"identifier\"") &&
294
0
         strstr(fileOrDef, "\"boundingBox\"") &&
295
0
         strstr(fileOrDef, "\"tileMatrix\"")))
296
0
    {
297
0
        loadOk = oDoc.LoadMemory(fileOrDef);
298
0
    }
299
0
    else if (STARTS_WITH_CI(fileOrDef, "http://") ||
300
0
             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
0
    else
306
0
    {
307
0
        VSIStatBufL sStat;
308
0
        if (VSIStatL(fileOrDef, &sStat) == 0)
309
0
        {
310
0
            loadOk = oDoc.Load(fileOrDef);
311
0
        }
312
0
        else
313
0
        {
314
0
            const char *pszFilename = CPLFindFile(
315
0
                "gdal", (std::string("tms_") + fileOrDef + ".json").c_str());
316
0
            if (pszFilename)
317
0
            {
318
0
                loadOk = oDoc.Load(pszFilename);
319
0
            }
320
0
            else
321
0
            {
322
0
                CPLError(CE_Failure, CPLE_AppDefined,
323
0
                         "Invalid tiling matrix set name");
324
0
            }
325
0
        }
326
0
    }
327
0
    if (!loadOk)
328
0
    {
329
0
        return nullptr;
330
0
    }
331
332
0
    auto oRoot = oDoc.GetRoot();
333
0
    const bool bIsTMSv2 =
334
0
        oRoot.GetObj("crs").IsValid() && oRoot.GetObj("tileMatrices").IsValid();
335
336
0
    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
0
    const auto GetCRS = [](const CPLJSONObject &j)
345
0
    {
346
0
        if (j.IsValid())
347
0
        {
348
0
            if (j.GetType() == CPLJSONObject::Type::String)
349
0
                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
0
        }
374
0
        return std::string();
375
0
    };
376
377
0
    poTMS->mIdentifier = oRoot.GetString(bIsTMSv2 ? "id" : "identifier");
378
0
    poTMS->mTitle = oRoot.GetString("title");
379
0
    poTMS->mAbstract = oRoot.GetString(bIsTMSv2 ? "description" : "abstract");
380
0
    const auto oBbox = oRoot.GetObj("boundingBox");
381
0
    if (oBbox.IsValid())
382
0
    {
383
0
        poTMS->mBbox.mCrs = GetCRS(oBbox.GetObj("crs"));
384
0
        const auto oLowerCorner = oBbox.GetArray("lowerCorner");
385
0
        if (oLowerCorner.IsValid() && oLowerCorner.Size() == 2)
386
0
        {
387
0
            poTMS->mBbox.mLowerCornerX = oLowerCorner[0].ToDouble(NaN);
388
0
            poTMS->mBbox.mLowerCornerY = oLowerCorner[1].ToDouble(NaN);
389
0
        }
390
0
        const auto oUpperCorner = oBbox.GetArray("upperCorner");
391
0
        if (oUpperCorner.IsValid() && oUpperCorner.Size() == 2)
392
0
        {
393
0
            poTMS->mBbox.mUpperCornerX = oUpperCorner[0].ToDouble(NaN);
394
0
            poTMS->mBbox.mUpperCornerY = oUpperCorner[1].ToDouble(NaN);
395
0
        }
396
0
    }
397
0
    poTMS->mCrs = GetCRS(oRoot.GetObj(bIsTMSv2 ? "crs" : "supportedCRS"));
398
0
    poTMS->mWellKnownScaleSet = oRoot.GetString("wellKnownScaleSet");
399
400
0
    OGRSpatialReference oCrs;
401
0
    if (oCrs.SetFromUserInput(
402
0
            poTMS->mCrs.c_str(),
403
0
            OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS) !=
404
0
        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
0
    double dfMetersPerUnit = 1.0;
411
0
    if (oCrs.IsProjected())
412
0
    {
413
0
        dfMetersPerUnit = oCrs.GetLinearUnits();
414
0
    }
415
0
    else if (oCrs.IsGeographic())
416
0
    {
417
0
        dfMetersPerUnit = oCrs.GetSemiMajor() * M_PI / 180;
418
0
    }
419
420
0
    const auto oTileMatrices =
421
0
        oRoot.GetArray(bIsTMSv2 ? "tileMatrices" : "tileMatrix");
422
0
    if (oTileMatrices.IsValid())
423
0
    {
424
0
        double dfLastScaleDenominator = std::numeric_limits<double>::max();
425
0
        for (const auto &oTM : oTileMatrices)
426
0
        {
427
0
            TileMatrix tm;
428
0
            tm.mId = oTM.GetString(bIsTMSv2 ? "id" : "identifier");
429
0
            tm.mScaleDenominator = oTM.GetDouble("scaleDenominator");
430
0
            if (tm.mScaleDenominator >= dfLastScaleDenominator ||
431
0
                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
0
            dfLastScaleDenominator = tm.mScaleDenominator;
439
            // See note g of Table 2 of
440
            // http://docs.opengeospatial.org/is/17-083r2/17-083r2.html
441
0
            tm.mResX = tm.mScaleDenominator * 0.28e-3 / dfMetersPerUnit;
442
0
            tm.mResY = tm.mResX;
443
0
            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
0
            const auto oTopLeftCorner =
454
0
                oTM.GetArray(bIsTMSv2 ? "pointOfOrigin" : "topLeftCorner");
455
0
            if (oTopLeftCorner.IsValid() && oTopLeftCorner.Size() == 2)
456
0
            {
457
0
                tm.mTopLeftX = oTopLeftCorner[0].ToDouble(NaN);
458
0
                tm.mTopLeftY = oTopLeftCorner[1].ToDouble(NaN);
459
0
            }
460
0
            tm.mTileWidth = oTM.GetInteger("tileWidth");
461
0
            if (tm.mTileWidth <= 0)
462
0
            {
463
0
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileWidth: %d",
464
0
                         tm.mTileWidth);
465
0
                return nullptr;
466
0
            }
467
0
            tm.mTileHeight = oTM.GetInteger("tileHeight");
468
0
            if (tm.mTileHeight <= 0)
469
0
            {
470
0
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileHeight: %d",
471
0
                         tm.mTileHeight);
472
0
                return nullptr;
473
0
            }
474
0
            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
0
            tm.mMatrixWidth = oTM.GetInteger("matrixWidth");
483
0
            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
0
            tm.mMatrixHeight = oTM.GetInteger("matrixHeight");
490
0
            if (tm.mMatrixHeight <= 0)
491
0
            {
492
0
                CPLError(CE_Failure, CPLE_AppDefined,
493
0
                         "Invalid matrixHeight: %d", tm.mMatrixHeight);
494
0
                return nullptr;
495
0
            }
496
497
0
            const auto oVariableMatrixWidths = oTM.GetArray(
498
0
                bIsTMSv2 ? "variableMatrixWidths" : "variableMatrixWidth");
499
0
            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
0
            poTMS->mTileMatrixList.emplace_back(std::move(tm));
512
0
        }
513
0
    }
514
0
    if (poTMS->mTileMatrixList.empty())
515
0
    {
516
0
        CPLError(CE_Failure, CPLE_AppDefined, "No tileMatrix defined");
517
0
        return nullptr;
518
0
    }
519
520
0
    return poTMS;
521
0
}
522
523
/************************************************************************/
524
/*                      haveAllLevelsSameTopLeft()                      */
525
/************************************************************************/
526
527
bool TileMatrixSet::haveAllLevelsSameTopLeft() const
528
0
{
529
0
    for (const auto &oTM : mTileMatrixList)
530
0
    {
531
0
        if (oTM.mTopLeftX != mTileMatrixList[0].mTopLeftX ||
532
0
            oTM.mTopLeftY != mTileMatrixList[0].mTopLeftY)
533
0
        {
534
0
            return false;
535
0
        }
536
0
    }
537
0
    return true;
538
0
}
539
540
/************************************************************************/
541
/*                     haveAllLevelsSameTileSize()                      */
542
/************************************************************************/
543
544
bool TileMatrixSet::haveAllLevelsSameTileSize() const
545
0
{
546
0
    for (const auto &oTM : mTileMatrixList)
547
0
    {
548
0
        if (oTM.mTileWidth != mTileMatrixList[0].mTileWidth ||
549
0
            oTM.mTileHeight != mTileMatrixList[0].mTileHeight)
550
0
        {
551
0
            return false;
552
0
        }
553
0
    }
554
0
    return true;
555
0
}
556
557
/************************************************************************/
558
/*                   hasOnlyPowerOfTwoVaryingScales()                   */
559
/************************************************************************/
560
561
bool TileMatrixSet::hasOnlyPowerOfTwoVaryingScales() const
562
0
{
563
0
    for (size_t i = 1; i < mTileMatrixList.size(); i++)
564
0
    {
565
0
        if (mTileMatrixList[i].mScaleDenominator == 0 ||
566
0
            std::fabs(mTileMatrixList[i - 1].mScaleDenominator /
567
0
                          mTileMatrixList[i].mScaleDenominator -
568
0
                      2) > 1e-10)
569
0
        {
570
0
            return false;
571
0
        }
572
0
    }
573
0
    return true;
574
0
}
575
576
/************************************************************************/
577
/*                       hasVariableMatrixWidth()                       */
578
/************************************************************************/
579
580
bool TileMatrixSet::hasVariableMatrixWidth() const
581
0
{
582
0
    for (const auto &oTM : mTileMatrixList)
583
0
    {
584
0
        if (!oTM.mVariableMatrixWidthList.empty())
585
0
        {
586
0
            return true;
587
0
        }
588
0
    }
589
0
    return false;
590
0
}
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