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

Generated by: LCOV version 1.14