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-09-10 17:48:50 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         674 : std::vector<std::string> TileMatrixSet::listPredefinedTileMatrixSets()
      34             : {
      35             :     std::vector<std::string> l{"GoogleMapsCompatible", "WorldCRS84Quad",
      36             :                                "WorldMercatorWGS84Quad", "GoogleCRS84Quad",
      37        5392 :                                "PseudoTMS_GlobalMercator"};
      38         674 :     const char *pszSomeFile = CPLFindFile("gdal", "tms_NZTM2000.json");
      39         674 :     if (pszSomeFile)
      40             :     {
      41        1348 :         std::set<std::string> set;
      42             :         CPLStringList aosList(
      43        1348 :             VSIReadDir(CPLGetDirnameSafe(pszSomeFile).c_str()));
      44      110536 :         for (int i = 0; i < aosList.size(); i++)
      45             :         {
      46      109862 :             const size_t nLen = strlen(aosList[i]);
      47      217028 :             if (nLen > strlen("tms_") + strlen(".json") &&
      48      112558 :                 STARTS_WITH(aosList[i], "tms_") &&
      49        2696 :                 EQUAL(aosList[i] + nLen - strlen(".json"), ".json"))
      50             :             {
      51        5392 :                 std::string id(aosList[i] + strlen("tms_"),
      52        5392 :                                nLen - (strlen("tms_") + strlen(".json")));
      53        2696 :                 set.insert(std::move(id));
      54             :             }
      55             :         }
      56        3370 :         for (const std::string &id : set)
      57        2696 :             l.push_back(id);
      58             :     }
      59         674 :     return l;
      60             : }
      61             : 
      62             : /************************************************************************/
      63             : /*                              parse()                                 */
      64             : /************************************************************************/
      65             : 
      66        6347 : std::unique_ptr<TileMatrixSet> TileMatrixSet::parse(const char *fileOrDef)
      67             : {
      68       12694 :     CPLJSONDocument oDoc;
      69       12694 :     std::unique_ptr<TileMatrixSet> poTMS(new TileMatrixSet());
      70             : 
      71        6347 :     constexpr double HALF_CIRCUMFERENCE = 6378137 * M_PI;
      72             : 
      73        6347 :     if (EQUAL(fileOrDef, "GoogleMapsCompatible") ||
      74        5483 :         EQUAL(fileOrDef, "WebMercatorQuad") ||
      75        5483 :         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         864 :         poTMS->mTitle = "GoogleMapsCompatible";
      83         864 :         poTMS->mIdentifier = "GoogleMapsCompatible";
      84         864 :         poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
      85         864 :         poTMS->mBbox.mCrs = poTMS->mCrs;
      86         864 :         poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
      87         864 :         poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
      88         864 :         poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
      89         864 :         poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
      90         864 :         poTMS->mWellKnownScaleSet =
      91         864 :             "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible";
      92       27648 :         for (int i = 0; i <= 30; i++)
      93             :         {
      94       53568 :             TileMatrix tm;
      95       26784 :             tm.mId = CPLSPrintf("%d", i);
      96       26784 :             tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
      97       26784 :             tm.mResY = tm.mResX;
      98       26784 :             tm.mScaleDenominator = tm.mResX / 0.28e-3;
      99       26784 :             tm.mTopLeftX = -HALF_CIRCUMFERENCE;
     100       26784 :             tm.mTopLeftY = HALF_CIRCUMFERENCE;
     101       26784 :             tm.mTileWidth = 256;
     102       26784 :             tm.mTileHeight = 256;
     103       26784 :             tm.mMatrixWidth = 1 << i;
     104       26784 :             tm.mMatrixHeight = 1 << i;
     105       26784 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     106             :         }
     107         864 :         return poTMS;
     108             :     }
     109             : 
     110        5483 :     if (EQUAL(fileOrDef, "WorldMercatorWGS84Quad") ||
     111        4810 :         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         673 :         poTMS->mTitle = "WorldMercatorWGS84Quad";
     116         673 :         poTMS->mIdentifier = "WorldMercatorWGS84Quad";
     117         673 :         poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3395";
     118         673 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     119         673 :         poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
     120         673 :         poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
     121         673 :         poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
     122         673 :         poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
     123         673 :         poTMS->mWellKnownScaleSet =
     124         673 :             "http://www.opengis.net/def/wkss/OGC/1.0/WorldMercatorWGS84Quad";
     125       21536 :         for (int i = 0; i <= 30; i++)
     126             :         {
     127       41726 :             TileMatrix tm;
     128       20863 :             tm.mId = CPLSPrintf("%d", i);
     129       20863 :             tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
     130       20863 :             tm.mResY = tm.mResX;
     131       20863 :             tm.mScaleDenominator = tm.mResX / 0.28e-3;
     132       20863 :             tm.mTopLeftX = -HALF_CIRCUMFERENCE;
     133       20863 :             tm.mTopLeftY = HALF_CIRCUMFERENCE;
     134       20863 :             tm.mTileWidth = 256;
     135       20863 :             tm.mTileHeight = 256;
     136       20863 :             tm.mMatrixWidth = 1 << i;
     137       20863 :             tm.mMatrixHeight = 1 << i;
     138       20863 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     139             :         }
     140         673 :         return poTMS;
     141             :     }
     142             : 
     143        4810 :     if (EQUAL(fileOrDef, "PseudoTMS_GlobalMercator"))
     144             :     {
     145             :         /* See global-mercator at
     146             :            http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
     147         673 :         poTMS->mTitle = "PseudoTMS_GlobalMercator";
     148         673 :         poTMS->mIdentifier = "PseudoTMS_GlobalMercator";
     149         673 :         poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
     150         673 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     151         673 :         poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
     152         673 :         poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
     153         673 :         poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
     154         673 :         poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
     155       20863 :         for (int i = 0; i <= 29; i++)
     156             :         {
     157       40380 :             TileMatrix tm;
     158       20190 :             tm.mId = CPLSPrintf("%d", i);
     159       20190 :             tm.mResX = HALF_CIRCUMFERENCE / 256 / (1 << i);
     160       20190 :             tm.mResY = tm.mResX;
     161       20190 :             tm.mScaleDenominator = tm.mResX / 0.28e-3;
     162       20190 :             tm.mTopLeftX = -HALF_CIRCUMFERENCE;
     163       20190 :             tm.mTopLeftY = HALF_CIRCUMFERENCE;
     164       20190 :             tm.mTileWidth = 256;
     165       20190 :             tm.mTileHeight = 256;
     166       20190 :             tm.mMatrixWidth = 2 * (1 << i);
     167       20190 :             tm.mMatrixHeight = 2 * (1 << i);
     168       20190 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     169             :         }
     170         673 :         return poTMS;
     171             :     }
     172             : 
     173        4137 :     if (EQUAL(fileOrDef, "InspireCRS84Quad") ||
     174        4131 :         EQUAL(fileOrDef, "PseudoTMS_GlobalGeodetic") ||
     175        4131 :         EQUAL(fileOrDef, "WorldCRS84Quad") ||
     176        3456 :         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         681 :         poTMS->mTitle = "WorldCRS84Quad";
     188         681 :         poTMS->mIdentifier = "WorldCRS84Quad";
     189         681 :         poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
     190         681 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     191         681 :         poTMS->mBbox.mLowerCornerX = -180;
     192         681 :         poTMS->mBbox.mLowerCornerY = -90;
     193         681 :         poTMS->mBbox.mUpperCornerX = 180;
     194         681 :         poTMS->mBbox.mUpperCornerY = 90;
     195         681 :         poTMS->mWellKnownScaleSet =
     196         681 :             "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       21111 :         for (int i = 0; i <= 29; i++)
     200             :         {
     201       40860 :             TileMatrix tm;
     202       20430 :             tm.mId = CPLSPrintf("%d", i);
     203       20430 :             tm.mResX = 180. / 256 / (1 << i);
     204       20430 :             tm.mResY = tm.mResX;
     205       20430 :             tm.mScaleDenominator =
     206       20430 :                 tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
     207       20430 :             tm.mTopLeftX = -180;
     208       20430 :             tm.mTopLeftY = 90;
     209       20430 :             tm.mTileWidth = 256;
     210       20430 :             tm.mTileHeight = 256;
     211       20430 :             tm.mMatrixWidth = 2 * (1 << i);
     212       20430 :             tm.mMatrixHeight = 1 << i;
     213       20430 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     214             :         }
     215         681 :         return poTMS;
     216             :     }
     217             : 
     218        3456 :     if (EQUAL(fileOrDef, "GoogleCRS84Quad") ||
     219        2765 :         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         691 :         poTMS->mTitle = "GoogleCRS84Quad";
     225         691 :         poTMS->mIdentifier = "GoogleCRS84Quad";
     226         691 :         poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
     227         691 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     228         691 :         poTMS->mBbox.mLowerCornerX = -180;
     229         691 :         poTMS->mBbox.mLowerCornerY = -90;
     230         691 :         poTMS->mBbox.mUpperCornerX = 180;
     231         691 :         poTMS->mBbox.mUpperCornerY = 90;
     232         691 :         poTMS->mWellKnownScaleSet =
     233         691 :             "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad";
     234       22112 :         for (int i = 0; i <= 30; i++)
     235             :         {
     236       42842 :             TileMatrix tm;
     237       21421 :             tm.mId = CPLSPrintf("%d", i);
     238       21421 :             tm.mResX = 360. / 256 / (1 << i);
     239       21421 :             tm.mResY = tm.mResX;
     240       21421 :             tm.mScaleDenominator =
     241       21421 :                 tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
     242       21421 :             tm.mTopLeftX = -180;
     243       21421 :             tm.mTopLeftY = 180;
     244       21421 :             tm.mTileWidth = 256;
     245       21421 :             tm.mTileHeight = 256;
     246       21421 :             tm.mMatrixWidth = 1 << i;
     247       21421 :             tm.mMatrixHeight = 1 << i;
     248       21421 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     249             :         }
     250         691 :         return poTMS;
     251             :     }
     252             : 
     253        2765 :     bool loadOk = false;
     254        2765 :     if (  // TMS 2.0 spec
     255        2765 :         (strstr(fileOrDef, "\"crs\"") &&
     256          47 :          strstr(fileOrDef, "\"tileMatrices\"")) ||
     257             :         // TMS 1.0 spec
     258        2743 :         (strstr(fileOrDef, "\"type\"") &&
     259          33 :          strstr(fileOrDef, "\"TileMatrixSetType\"")) ||
     260        2710 :         (strstr(fileOrDef, "\"identifier\"") &&
     261           1 :          strstr(fileOrDef, "\"boundingBox\"") &&
     262           1 :          strstr(fileOrDef, "\"tileMatrix\"")))
     263             :     {
     264          56 :         loadOk = oDoc.LoadMemory(fileOrDef);
     265             :     }
     266        2709 :     else if (STARTS_WITH_CI(fileOrDef, "http://") ||
     267        2708 :              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        2708 :         if (VSIStatL(fileOrDef, &sStat) == 0)
     276             :         {
     277           1 :             loadOk = oDoc.Load(fileOrDef);
     278             :         }
     279             :         else
     280             :         {
     281        2707 :             const char *pszFilename = CPLFindFile(
     282        5414 :                 "gdal", (std::string("tms_") + fileOrDef + ".json").c_str());
     283        2707 :             if (pszFilename)
     284             :             {
     285        2705 :                 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        2765 :     if (!loadOk)
     295             :     {
     296           4 :         return nullptr;
     297             :     }
     298             : 
     299        5522 :     auto oRoot = oDoc.GetRoot();
     300             :     const bool bIsTMSv2 =
     301        2761 :         oRoot.GetObj("crs").IsValid() && oRoot.GetObj("tileMatrices").IsValid();
     302             : 
     303        5523 :     if (!bIsTMSv2 && oRoot.GetString("type") != "TileMatrixSetType" &&
     304        2762 :         !oRoot.GetObj("tileMatrix").IsValid())
     305             :     {
     306           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     307             :                  "Expected type = TileMatrixSetType");
     308           0 :         return nullptr;
     309             :     }
     310             : 
     311        3466 :     const auto GetCRS = [](const CPLJSONObject &j)
     312             :     {
     313        3466 :         if (j.IsValid())
     314             :         {
     315        3465 :             if (j.GetType() == CPLJSONObject::Type::String)
     316        3462 :                 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        2761 :     poTMS->mIdentifier = oRoot.GetString(bIsTMSv2 ? "id" : "identifier");
     345        2761 :     poTMS->mTitle = oRoot.GetString("title");
     346        2761 :     poTMS->mAbstract = oRoot.GetString(bIsTMSv2 ? "description" : "abstract");
     347        8283 :     const auto oBbox = oRoot.GetObj("boundingBox");
     348        2761 :     if (oBbox.IsValid())
     349             :     {
     350         705 :         poTMS->mBbox.mCrs = GetCRS(oBbox.GetObj("crs"));
     351        2115 :         const auto oLowerCorner = oBbox.GetArray("lowerCorner");
     352         705 :         if (oLowerCorner.IsValid() && oLowerCorner.Size() == 2)
     353             :         {
     354         705 :             poTMS->mBbox.mLowerCornerX = oLowerCorner[0].ToDouble(NaN);
     355         705 :             poTMS->mBbox.mLowerCornerY = oLowerCorner[1].ToDouble(NaN);
     356             :         }
     357        2115 :         const auto oUpperCorner = oBbox.GetArray("upperCorner");
     358         705 :         if (oUpperCorner.IsValid() && oUpperCorner.Size() == 2)
     359             :         {
     360         705 :             poTMS->mBbox.mUpperCornerX = oUpperCorner[0].ToDouble(NaN);
     361         705 :             poTMS->mBbox.mUpperCornerY = oUpperCorner[1].ToDouble(NaN);
     362             :         }
     363             :     }
     364        2761 :     poTMS->mCrs = GetCRS(oRoot.GetObj(bIsTMSv2 ? "crs" : "supportedCRS"));
     365        2761 :     poTMS->mWellKnownScaleSet = oRoot.GetString("wellKnownScaleSet");
     366             : 
     367        5522 :     OGRSpatialReference oCrs;
     368        2761 :     if (oCrs.SetFromUserInput(
     369        2761 :             poTMS->mCrs.c_str(),
     370        2761 :             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        2760 :     double dfMetersPerUnit = 1.0;
     378        2760 :     if (oCrs.IsProjected())
     379             :     {
     380        2714 :         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        8280 :         oRoot.GetArray(bIsTMSv2 ? "tileMatrices" : "tileMatrix");
     389        2760 :     if (oTileMatrices.IsValid())
     390             :     {
     391        2760 :         double dfLastScaleDenominator = std::numeric_limits<double>::max();
     392       55397 :         for (const auto &oTM : oTileMatrices)
     393             :         {
     394       52643 :             TileMatrix tm;
     395       52643 :             tm.mId = oTM.GetString(bIsTMSv2 ? "id" : "identifier");
     396       52643 :             tm.mScaleDenominator = oTM.GetDouble("scaleDenominator");
     397       52643 :             if (tm.mScaleDenominator >= dfLastScaleDenominator ||
     398       52643 :                 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       52642 :             dfLastScaleDenominator = tm.mScaleDenominator;
     406             :             // See note g of Table 2 of
     407             :             // http://docs.opengeospatial.org/is/17-083r2/17-083r2.html
     408       52642 :             tm.mResX = tm.mScaleDenominator * 0.28e-3 / dfMetersPerUnit;
     409       52642 :             tm.mResY = tm.mResX;
     410       52642 :             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      105284 :                 oTM.GetArray(bIsTMSv2 ? "pointOfOrigin" : "topLeftCorner");
     422       52642 :             if (oTopLeftCorner.IsValid() && oTopLeftCorner.Size() == 2)
     423             :             {
     424       52642 :                 tm.mTopLeftX = oTopLeftCorner[0].ToDouble(NaN);
     425       52642 :                 tm.mTopLeftY = oTopLeftCorner[1].ToDouble(NaN);
     426             :             }
     427       52642 :             tm.mTileWidth = oTM.GetInteger("tileWidth");
     428       52642 :             if (tm.mTileWidth <= 0)
     429             :             {
     430           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileWidth: %d",
     431             :                          tm.mTileWidth);
     432           1 :                 return nullptr;
     433             :             }
     434       52641 :             tm.mTileHeight = oTM.GetInteger("tileHeight");
     435       52641 :             if (tm.mTileHeight <= 0)
     436             :             {
     437           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileHeight: %d",
     438             :                          tm.mTileHeight);
     439           1 :                 return nullptr;
     440             :             }
     441       52640 :             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       52639 :             tm.mMatrixWidth = oTM.GetInteger("matrixWidth");
     450       52639 :             if (tm.mMatrixWidth <= 0)
     451             :             {
     452           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid matrixWidth: %d",
     453             :                          tm.mMatrixWidth);
     454           1 :                 return nullptr;
     455             :             }
     456       52638 :             tm.mMatrixHeight = oTM.GetInteger("matrixHeight");
     457       52638 :             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      157911 :                 bIsTMSv2 ? "variableMatrixWidths" : "variableMatrixWidth");
     466       52637 :             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       52637 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     479             :         }
     480             :     }
     481        2754 :     if (poTMS->mTileMatrixList.empty())
     482             :     {
     483           0 :         CPLError(CE_Failure, CPLE_AppDefined, "No tileMatrix defined");
     484           0 :         return nullptr;
     485             :     }
     486             : 
     487        2754 :     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        5796 : bool TileMatrixSet::hasVariableMatrixWidth() const
     548             : {
     549      156792 :     for (const auto &oTM : mTileMatrixList)
     550             :     {
     551      150998 :         if (!oTM.mVariableMatrixWidthList.empty())
     552             :         {
     553           2 :             return true;
     554             :         }
     555             :     }
     556        5794 :     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          41 : std::string TileMatrixSet::exportToTMSJsonV1() const
     607             : {
     608          82 :     CPLJSONObject oRoot;
     609          41 :     oRoot["type"] = "TileMatrixSetType";
     610          41 :     oRoot["title"] = mTitle;
     611          41 :     oRoot["identifier"] = mIdentifier;
     612          41 :     if (!mAbstract.empty())
     613           0 :         oRoot["abstract"] = mAbstract;
     614          41 :     if (!std::isnan(mBbox.mLowerCornerX))
     615             :     {
     616          41 :         CPLJSONObject oBbox;
     617          41 :         oBbox["type"] = "BoundingBoxType";
     618          41 :         oBbox["crs"] = mBbox.mCrs;
     619          41 :         oBbox["lowerCorner"] = {mBbox.mLowerCornerX, mBbox.mLowerCornerY};
     620          41 :         oBbox["upperCorner"] = {mBbox.mUpperCornerX, mBbox.mUpperCornerY};
     621          41 :         oRoot["boundingBox"] = std::move(oBbox);
     622             :     }
     623          41 :     oRoot["supportedCRS"] = mCrs;
     624          41 :     if (!mWellKnownScaleSet.empty())
     625          37 :         oRoot["wellKnownScaleSet"] = mWellKnownScaleSet;
     626             : 
     627          41 :     CPLJSONArray oTileMatrices;
     628         119 :     for (const auto &tm : mTileMatrixList)
     629             :     {
     630         156 :         CPLJSONObject oTM;
     631          78 :         oTM["type"] = "TileMatrixType";
     632          78 :         oTM["identifier"] = tm.mId;
     633          78 :         oTM["scaleDenominator"] = tm.mScaleDenominator;
     634          78 :         oTM["topLeftCorner"] = {tm.mTopLeftX, tm.mTopLeftY};
     635          78 :         oTM["tileWidth"] = tm.mTileWidth;
     636          78 :         oTM["tileHeight"] = tm.mTileHeight;
     637          78 :         oTM["matrixWidth"] = tm.mMatrixWidth;
     638          78 :         oTM["matrixHeight"] = tm.mMatrixHeight;
     639             : 
     640          78 :         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          78 :         oTileMatrices.Add(oTM);
     655             :     }
     656          41 :     oRoot["tileMatrix"] = oTileMatrices;
     657          82 :     return CPLString(oRoot.Format(CPLJSONObject::PrettyFormat::Pretty))
     658          82 :         .replaceAll("\\/", '/');
     659             : }
     660             : 
     661             : }  // namespace gdal
     662             : 
     663             : //! @endcond

Generated by: LCOV version 1.14