LCOV - code coverage report
Current view: top level - gcore - tilematrixset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 396 402 98.5 %
Date: 2026-06-05 12:31:37 Functions: 9 9 100.0 %

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

Generated by: LCOV version 1.14