Coverage Report

Created: 2026-02-14 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> TileMatrixSet::listPredefinedTileMatrixSets()
34
2
{
35
2
    std::vector<std::string> l{"GoogleMapsCompatible", "WorldCRS84Quad",
36
2
                               "WorldMercatorWGS84Quad", "GoogleCRS84Quad",
37
2
                               "PseudoTMS_GlobalMercator"};
38
2
    const char *pszSomeFile = CPLFindFile("gdal", "tms_NZTM2000.json");
39
2
    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
2
    return l;
60
2
}
61
62
/************************************************************************/
63
/*                               parse()                                */
64
/************************************************************************/
65
66
std::unique_ptr<TileMatrixSet> TileMatrixSet::parse(const char *fileOrDef)
67
36
{
68
36
    CPLJSONDocument oDoc;
69
36
    std::unique_ptr<TileMatrixSet> poTMS(new TileMatrixSet());
70
71
36
    constexpr double HALF_CIRCUMFERENCE = 6378137 * M_PI;
72
73
36
    if (EQUAL(fileOrDef, "GoogleMapsCompatible") ||
74
34
        EQUAL(fileOrDef, "WebMercatorQuad") ||
75
34
        EQUAL(
76
36
            fileOrDef,
77
36
            "http://www.opengis.net/def/tilematrixset/OGC/1.0/WebMercatorQuad"))
78
2
    {
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
2
        poTMS->mTitle = "GoogleMapsCompatible";
83
2
        poTMS->mIdentifier = "GoogleMapsCompatible";
84
2
        poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
85
2
        poTMS->mBbox.mCrs = poTMS->mCrs;
86
2
        poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
87
2
        poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
88
2
        poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
89
2
        poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
90
2
        poTMS->mWellKnownScaleSet =
91
2
            "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible";
92
64
        for (int i = 0; i <= 30; i++)
93
62
        {
94
62
            TileMatrix tm;
95
62
            tm.mId = CPLSPrintf("%d", i);
96
62
            tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
97
62
            tm.mResY = tm.mResX;
98
62
            tm.mScaleDenominator = tm.mResX / 0.28e-3;
99
62
            tm.mTopLeftX = -HALF_CIRCUMFERENCE;
100
62
            tm.mTopLeftY = HALF_CIRCUMFERENCE;
101
62
            tm.mTileWidth = 256;
102
62
            tm.mTileHeight = 256;
103
62
            tm.mMatrixWidth = 1 << i;
104
62
            tm.mMatrixHeight = 1 << i;
105
62
            poTMS->mTileMatrixList.emplace_back(std::move(tm));
106
62
        }
107
2
        return poTMS;
108
2
    }
109
110
34
    if (EQUAL(fileOrDef, "WorldMercatorWGS84Quad") ||
111
32
        EQUAL(fileOrDef, "http://www.opengis.net/def/tilematrixset/OGC/1.0/"
112
34
                         "WorldMercatorWGS84Quad"))
113
2
    {
114
        // See https://docs.ogc.org/is/17-083r4/17-083r4.html#toc51
115
2
        poTMS->mTitle = "WorldMercatorWGS84Quad";
116
2
        poTMS->mIdentifier = "WorldMercatorWGS84Quad";
117
2
        poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3395";
118
2
        poTMS->mBbox.mCrs = poTMS->mCrs;
119
2
        poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
120
2
        poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
121
2
        poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
122
2
        poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
123
2
        poTMS->mWellKnownScaleSet =
124
2
            "http://www.opengis.net/def/wkss/OGC/1.0/WorldMercatorWGS84Quad";
125
64
        for (int i = 0; i <= 30; i++)
126
62
        {
127
62
            TileMatrix tm;
128
62
            tm.mId = CPLSPrintf("%d", i);
129
62
            tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
130
62
            tm.mResY = tm.mResX;
131
62
            tm.mScaleDenominator = tm.mResX / 0.28e-3;
132
62
            tm.mTopLeftX = -HALF_CIRCUMFERENCE;
133
62
            tm.mTopLeftY = HALF_CIRCUMFERENCE;
134
62
            tm.mTileWidth = 256;
135
62
            tm.mTileHeight = 256;
136
62
            tm.mMatrixWidth = 1 << i;
137
62
            tm.mMatrixHeight = 1 << i;
138
62
            poTMS->mTileMatrixList.emplace_back(std::move(tm));
139
62
        }
140
2
        return poTMS;
141
2
    }
142
143
32
    if (EQUAL(fileOrDef, "PseudoTMS_GlobalMercator"))
144
2
    {
145
        /* See global-mercator at
146
           http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
147
2
        poTMS->mTitle = "PseudoTMS_GlobalMercator";
148
2
        poTMS->mIdentifier = "PseudoTMS_GlobalMercator";
149
2
        poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
150
2
        poTMS->mBbox.mCrs = poTMS->mCrs;
151
2
        poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
152
2
        poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
153
2
        poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
154
2
        poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
155
62
        for (int i = 0; i <= 29; i++)
156
60
        {
157
60
            TileMatrix tm;
158
60
            tm.mId = CPLSPrintf("%d", i);
159
60
            tm.mResX = HALF_CIRCUMFERENCE / 256 / (1 << i);
160
60
            tm.mResY = tm.mResX;
161
60
            tm.mScaleDenominator = tm.mResX / 0.28e-3;
162
60
            tm.mTopLeftX = -HALF_CIRCUMFERENCE;
163
60
            tm.mTopLeftY = HALF_CIRCUMFERENCE;
164
60
            tm.mTileWidth = 256;
165
60
            tm.mTileHeight = 256;
166
60
            tm.mMatrixWidth = 2 * (1 << i);
167
60
            tm.mMatrixHeight = 2 * (1 << i);
168
60
            poTMS->mTileMatrixList.emplace_back(std::move(tm));
169
60
        }
170
2
        return poTMS;
171
2
    }
172
173
30
    if (EQUAL(fileOrDef, "InspireCRS84Quad") ||
174
30
        EQUAL(fileOrDef, "PseudoTMS_GlobalGeodetic") ||
175
30
        EQUAL(fileOrDef, "WorldCRS84Quad") ||
176
28
        EQUAL(
177
30
            fileOrDef,
178
30
            "http://www.opengis.net/def/tilematrixset/OGC/1.0/WorldCRS84Quad"))
179
2
    {
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
2
        poTMS->mTitle = "WorldCRS84Quad";
188
2
        poTMS->mIdentifier = "WorldCRS84Quad";
189
2
        poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
190
2
        poTMS->mBbox.mCrs = poTMS->mCrs;
191
2
        poTMS->mBbox.mLowerCornerX = -180;
192
2
        poTMS->mBbox.mLowerCornerY = -90;
193
2
        poTMS->mBbox.mUpperCornerX = 180;
194
2
        poTMS->mBbox.mUpperCornerY = 90;
195
2
        poTMS->mWellKnownScaleSet =
196
2
            "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
62
        for (int i = 0; i <= 29; i++)
200
60
        {
201
60
            TileMatrix tm;
202
60
            tm.mId = CPLSPrintf("%d", i);
203
60
            tm.mResX = 180. / 256 / (1 << i);
204
60
            tm.mResY = tm.mResX;
205
60
            tm.mScaleDenominator =
206
60
                tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
207
60
            tm.mTopLeftX = -180;
208
60
            tm.mTopLeftY = 90;
209
60
            tm.mTileWidth = 256;
210
60
            tm.mTileHeight = 256;
211
60
            tm.mMatrixWidth = 2 * (1 << i);
212
60
            tm.mMatrixHeight = 1 << i;
213
60
            poTMS->mTileMatrixList.emplace_back(std::move(tm));
214
60
        }
215
2
        return poTMS;
216
2
    }
217
218
28
    if (EQUAL(fileOrDef, "GoogleCRS84Quad") ||
219
26
        EQUAL(fileOrDef,
220
28
              "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad"))
221
2
    {
222
        /* See http://portal.opengeospatial.org/files/?artifact_id=35326 (WMTS 1.0),
223
               Annex E.3 */
224
2
        poTMS->mTitle = "GoogleCRS84Quad";
225
2
        poTMS->mIdentifier = "GoogleCRS84Quad";
226
2
        poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
227
2
        poTMS->mBbox.mCrs = poTMS->mCrs;
228
2
        poTMS->mBbox.mLowerCornerX = -180;
229
2
        poTMS->mBbox.mLowerCornerY = -90;
230
2
        poTMS->mBbox.mUpperCornerX = 180;
231
2
        poTMS->mBbox.mUpperCornerY = 90;
232
2
        poTMS->mWellKnownScaleSet =
233
2
            "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad";
234
64
        for (int i = 0; i <= 30; i++)
235
62
        {
236
62
            TileMatrix tm;
237
62
            tm.mId = CPLSPrintf("%d", i);
238
62
            tm.mResX = 360. / 256 / (1 << i);
239
62
            tm.mResY = tm.mResX;
240
62
            tm.mScaleDenominator =
241
62
                tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
242
62
            tm.mTopLeftX = -180;
243
62
            tm.mTopLeftY = 180;
244
62
            tm.mTileWidth = 256;
245
62
            tm.mTileHeight = 256;
246
62
            tm.mMatrixWidth = 1 << i;
247
62
            tm.mMatrixHeight = 1 << i;
248
62
            poTMS->mTileMatrixList.emplace_back(std::move(tm));
249
62
        }
250
2
        return poTMS;
251
2
    }
252
253
26
    bool loadOk = false;
254
26
    if (  // TMS 2.0 spec
255
26
        (strstr(fileOrDef, "\"crs\"") &&
256
26
         strstr(fileOrDef, "\"tileMatrices\"")) ||
257
        // TMS 1.0 spec
258
26
        (strstr(fileOrDef, "\"type\"") &&
259
26
         strstr(fileOrDef, "\"TileMatrixSetType\"")) ||
260
1
        (strstr(fileOrDef, "\"identifier\"") &&
261
1
         strstr(fileOrDef, "\"boundingBox\"") &&
262
0
         strstr(fileOrDef, "\"tileMatrix\"")))
263
25
    {
264
25
        loadOk = oDoc.LoadMemory(fileOrDef);
265
25
    }
266
1
    else if (STARTS_WITH_CI(fileOrDef, "http://") ||
267
1
             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
1
    else
273
1
    {
274
1
        VSIStatBufL sStat;
275
1
        if (VSIStatL(fileOrDef, &sStat) == 0)
276
0
        {
277
0
            loadOk = oDoc.Load(fileOrDef);
278
0
        }
279
1
        else
280
1
        {
281
1
            const char *pszFilename = CPLFindFile(
282
1
                "gdal", (std::string("tms_") + fileOrDef + ".json").c_str());
283
1
            if (pszFilename)
284
0
            {
285
0
                loadOk = oDoc.Load(pszFilename);
286
0
            }
287
1
            else
288
1
            {
289
1
                CPLError(CE_Failure, CPLE_AppDefined,
290
1
                         "Invalid tiling matrix set name");
291
1
            }
292
1
        }
293
1
    }
294
26
    if (!loadOk)
295
1
    {
296
1
        return nullptr;
297
1
    }
298
299
25
    auto oRoot = oDoc.GetRoot();
300
25
    const bool bIsTMSv2 =
301
25
        oRoot.GetObj("crs").IsValid() && oRoot.GetObj("tileMatrices").IsValid();
302
303
25
    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
25
    const auto GetCRS = [](const CPLJSONObject &j)
312
48
    {
313
48
        if (j.IsValid())
314
48
        {
315
48
            if (j.GetType() == CPLJSONObject::Type::String)
316
48
                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
48
        }
341
0
        return std::string();
342
48
    };
343
344
25
    poTMS->mIdentifier = oRoot.GetString(bIsTMSv2 ? "id" : "identifier");
345
25
    poTMS->mTitle = oRoot.GetString("title");
346
25
    poTMS->mAbstract = oRoot.GetString(bIsTMSv2 ? "description" : "abstract");
347
25
    const auto oBbox = oRoot.GetObj("boundingBox");
348
25
    if (oBbox.IsValid())
349
23
    {
350
23
        poTMS->mBbox.mCrs = GetCRS(oBbox.GetObj("crs"));
351
23
        const auto oLowerCorner = oBbox.GetArray("lowerCorner");
352
23
        if (oLowerCorner.IsValid() && oLowerCorner.Size() == 2)
353
23
        {
354
23
            poTMS->mBbox.mLowerCornerX = oLowerCorner[0].ToDouble(NaN);
355
23
            poTMS->mBbox.mLowerCornerY = oLowerCorner[1].ToDouble(NaN);
356
23
        }
357
23
        const auto oUpperCorner = oBbox.GetArray("upperCorner");
358
23
        if (oUpperCorner.IsValid() && oUpperCorner.Size() == 2)
359
21
        {
360
21
            poTMS->mBbox.mUpperCornerX = oUpperCorner[0].ToDouble(NaN);
361
21
            poTMS->mBbox.mUpperCornerY = oUpperCorner[1].ToDouble(NaN);
362
21
        }
363
23
    }
364
25
    poTMS->mCrs = GetCRS(oRoot.GetObj(bIsTMSv2 ? "crs" : "supportedCRS"));
365
25
    poTMS->mWellKnownScaleSet = oRoot.GetString("wellKnownScaleSet");
366
367
25
    OGRSpatialReference oCrs;
368
25
    if (oCrs.SetFromUserInput(
369
25
            poTMS->mCrs.c_str(),
370
25
            OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS) !=
371
25
        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
25
    double dfMetersPerUnit = 1.0;
378
25
    if (oCrs.IsProjected())
379
0
    {
380
0
        dfMetersPerUnit = oCrs.GetLinearUnits();
381
0
    }
382
25
    else if (oCrs.IsGeographic())
383
25
    {
384
25
        dfMetersPerUnit = oCrs.GetSemiMajor() * M_PI / 180;
385
25
    }
386
387
25
    const auto oTileMatrices =
388
25
        oRoot.GetArray(bIsTMSv2 ? "tileMatrices" : "tileMatrix");
389
25
    if (oTileMatrices.IsValid())
390
25
    {
391
25
        double dfLastScaleDenominator = std::numeric_limits<double>::max();
392
25
        for (const auto &oTM : oTileMatrices)
393
74
        {
394
74
            TileMatrix tm;
395
74
            tm.mId = oTM.GetString(bIsTMSv2 ? "id" : "identifier");
396
74
            tm.mScaleDenominator = oTM.GetDouble("scaleDenominator");
397
74
            if (tm.mScaleDenominator >= dfLastScaleDenominator ||
398
74
                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
74
            dfLastScaleDenominator = tm.mScaleDenominator;
406
            // See note g of Table 2 of
407
            // http://docs.opengeospatial.org/is/17-083r2/17-083r2.html
408
74
            tm.mResX = tm.mScaleDenominator * 0.28e-3 / dfMetersPerUnit;
409
74
            tm.mResY = tm.mResX;
410
74
            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
74
            const auto oTopLeftCorner =
421
74
                oTM.GetArray(bIsTMSv2 ? "pointOfOrigin" : "topLeftCorner");
422
74
            if (oTopLeftCorner.IsValid() && oTopLeftCorner.Size() == 2)
423
71
            {
424
71
                tm.mTopLeftX = oTopLeftCorner[0].ToDouble(NaN);
425
71
                tm.mTopLeftY = oTopLeftCorner[1].ToDouble(NaN);
426
71
            }
427
74
            tm.mTileWidth = oTM.GetInteger("tileWidth");
428
74
            if (tm.mTileWidth <= 0)
429
1
            {
430
1
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileWidth: %d",
431
1
                         tm.mTileWidth);
432
1
                return nullptr;
433
1
            }
434
73
            tm.mTileHeight = oTM.GetInteger("tileHeight");
435
73
            if (tm.mTileHeight <= 0)
436
1
            {
437
1
                CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileHeight: %d",
438
1
                         tm.mTileHeight);
439
1
                return nullptr;
440
1
            }
441
72
            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
72
            tm.mMatrixWidth = oTM.GetInteger("matrixWidth");
450
72
            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
72
            tm.mMatrixHeight = oTM.GetInteger("matrixHeight");
457
72
            if (tm.mMatrixHeight <= 0)
458
1
            {
459
1
                CPLError(CE_Failure, CPLE_AppDefined,
460
1
                         "Invalid matrixHeight: %d", tm.mMatrixHeight);
461
1
                return nullptr;
462
1
            }
463
464
71
            const auto oVariableMatrixWidths = oTM.GetArray(
465
71
                bIsTMSv2 ? "variableMatrixWidths" : "variableMatrixWidth");
466
71
            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
71
            poTMS->mTileMatrixList.emplace_back(std::move(tm));
479
71
        }
480
25
    }
481
22
    if (poTMS->mTileMatrixList.empty())
482
0
    {
483
0
        CPLError(CE_Failure, CPLE_AppDefined, "No tileMatrix defined");
484
0
        return nullptr;
485
0
    }
486
487
22
    return poTMS;
488
22
}
489
490
/************************************************************************/
491
/*                      haveAllLevelsSameTopLeft()                      */
492
/************************************************************************/
493
494
bool TileMatrixSet::haveAllLevelsSameTopLeft() const
495
10
{
496
10
    for (const auto &oTM : mTileMatrixList)
497
306
    {
498
306
        if (oTM.mTopLeftX != mTileMatrixList[0].mTopLeftX ||
499
306
            oTM.mTopLeftY != mTileMatrixList[0].mTopLeftY)
500
0
        {
501
0
            return false;
502
0
        }
503
306
    }
504
10
    return true;
505
10
}
506
507
/************************************************************************/
508
/*                     haveAllLevelsSameTileSize()                      */
509
/************************************************************************/
510
511
bool TileMatrixSet::haveAllLevelsSameTileSize() const
512
10
{
513
10
    for (const auto &oTM : mTileMatrixList)
514
306
    {
515
306
        if (oTM.mTileWidth != mTileMatrixList[0].mTileWidth ||
516
306
            oTM.mTileHeight != mTileMatrixList[0].mTileHeight)
517
0
        {
518
0
            return false;
519
0
        }
520
306
    }
521
10
    return true;
522
10
}
523
524
/************************************************************************/
525
/*                   hasOnlyPowerOfTwoVaryingScales()                   */
526
/************************************************************************/
527
528
bool TileMatrixSet::hasOnlyPowerOfTwoVaryingScales() const
529
10
{
530
306
    for (size_t i = 1; i < mTileMatrixList.size(); i++)
531
296
    {
532
296
        if (mTileMatrixList[i].mScaleDenominator == 0 ||
533
296
            std::fabs(mTileMatrixList[i - 1].mScaleDenominator /
534
296
                          mTileMatrixList[i].mScaleDenominator -
535
296
                      2) > 1e-10)
536
0
        {
537
0
            return false;
538
0
        }
539
296
    }
540
10
    return true;
541
10
}
542
543
/************************************************************************/
544
/*                       hasVariableMatrixWidth()                       */
545
/************************************************************************/
546
547
bool TileMatrixSet::hasVariableMatrixWidth() const
548
10
{
549
10
    for (const auto &oTM : mTileMatrixList)
550
306
    {
551
306
        if (!oTM.mVariableMatrixWidthList.empty())
552
0
        {
553
0
            return true;
554
0
        }
555
306
    }
556
10
    return false;
557
10
}
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
/************************************************************************/
603
/*                         exportToTMSJsonV1()                          */
604
/************************************************************************/
605
606
std::string TileMatrixSet::exportToTMSJsonV1() const
607
0
{
608
0
    CPLJSONObject oRoot;
609
0
    oRoot["type"] = "TileMatrixSetType";
610
0
    oRoot["title"] = mTitle;
611
0
    oRoot["identifier"] = mIdentifier;
612
0
    if (!mAbstract.empty())
613
0
        oRoot["abstract"] = mAbstract;
614
0
    if (!std::isnan(mBbox.mLowerCornerX))
615
0
    {
616
0
        CPLJSONObject oBbox;
617
0
        oBbox["type"] = "BoundingBoxType";
618
0
        oBbox["crs"] = mBbox.mCrs;
619
0
        oBbox["lowerCorner"] = {mBbox.mLowerCornerX, mBbox.mLowerCornerY};
620
0
        oBbox["upperCorner"] = {mBbox.mUpperCornerX, mBbox.mUpperCornerY};
621
0
        oRoot["boundingBox"] = std::move(oBbox);
622
0
    }
623
0
    oRoot["supportedCRS"] = mCrs;
624
0
    if (!mWellKnownScaleSet.empty())
625
0
        oRoot["wellKnownScaleSet"] = mWellKnownScaleSet;
626
627
0
    CPLJSONArray oTileMatrices;
628
0
    for (const auto &tm : mTileMatrixList)
629
0
    {
630
0
        CPLJSONObject oTM;
631
0
        oTM["type"] = "TileMatrixType";
632
0
        oTM["identifier"] = tm.mId;
633
0
        oTM["scaleDenominator"] = tm.mScaleDenominator;
634
0
        oTM["topLeftCorner"] = {tm.mTopLeftX, tm.mTopLeftY};
635
0
        oTM["tileWidth"] = tm.mTileWidth;
636
0
        oTM["tileHeight"] = tm.mTileHeight;
637
0
        oTM["matrixWidth"] = tm.mMatrixWidth;
638
0
        oTM["matrixHeight"] = tm.mMatrixHeight;
639
640
0
        if (!tm.mVariableMatrixWidthList.empty())
641
0
        {
642
0
            CPLJSONArray oVariableMatrixWidths;
643
0
            for (const auto &vmw : tm.mVariableMatrixWidthList)
644
0
            {
645
0
                CPLJSONObject oVMW;
646
0
                oVMW["coalesce"] = vmw.mCoalesce;
647
0
                oVMW["minTileRow"] = vmw.mMinTileRow;
648
0
                oVMW["maxTileRow"] = vmw.mMaxTileRow;
649
0
                oVariableMatrixWidths.Add(oVMW);
650
0
            }
651
0
            oTM["variableMatrixWidth"] = oVariableMatrixWidths;
652
0
        }
653
654
0
        oTileMatrices.Add(oTM);
655
0
    }
656
0
    oRoot["tileMatrix"] = oTileMatrices;
657
0
    return CPLString(oRoot.Format(CPLJSONObject::PrettyFormat::Pretty))
658
0
        .replaceAll("\\/", '/');
659
0
}
660
661
}  // namespace gdal
662
663
//! @endcond