LCOV - code coverage report
Current view: top level - gcore - tilematrixset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 330 335 98.5 %
Date: 2025-07-08 21:33:46 Functions: 8 8 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         580 : std::vector<std::string> TileMatrixSet::listPredefinedTileMatrixSets()
      34             : {
      35             :     std::vector<std::string> l{"GoogleMapsCompatible", "WorldCRS84Quad",
      36             :                                "WorldMercatorWGS84Quad", "GoogleCRS84Quad",
      37        4640 :                                "PseudoTMS_GlobalMercator"};
      38         580 :     const char *pszSomeFile = CPLFindFile("gdal", "tms_NZTM2000.json");
      39         580 :     if (pszSomeFile)
      40             :     {
      41        1160 :         std::set<std::string> set;
      42             :         CPLStringList aosList(
      43        1160 :             VSIReadDir(CPLGetDirnameSafe(pszSomeFile).c_str()));
      44       95120 :         for (int i = 0; i < aosList.size(); i++)
      45             :         {
      46       94540 :             const size_t nLen = strlen(aosList[i]);
      47      186760 :             if (nLen > strlen("tms_") + strlen(".json") &&
      48       96860 :                 STARTS_WITH(aosList[i], "tms_") &&
      49        2320 :                 EQUAL(aosList[i] + nLen - strlen(".json"), ".json"))
      50             :             {
      51        4640 :                 std::string id(aosList[i] + strlen("tms_"),
      52        4640 :                                nLen - (strlen("tms_") + strlen(".json")));
      53        2320 :                 set.insert(std::move(id));
      54             :             }
      55             :         }
      56        2900 :         for (const std::string &id : set)
      57        2320 :             l.push_back(id);
      58             :     }
      59         580 :     return l;
      60             : }
      61             : 
      62             : /************************************************************************/
      63             : /*                              parse()                                 */
      64             : /************************************************************************/
      65             : 
      66        5451 : std::unique_ptr<TileMatrixSet> TileMatrixSet::parse(const char *fileOrDef)
      67             : {
      68       10902 :     CPLJSONDocument oDoc;
      69       10902 :     std::unique_ptr<TileMatrixSet> poTMS(new TileMatrixSet());
      70             : 
      71        5451 :     constexpr double HALF_CIRCUMFERENCE = 6378137 * M_PI;
      72             : 
      73        5451 :     if (EQUAL(fileOrDef, "GoogleMapsCompatible") ||
      74        4729 :         EQUAL(fileOrDef, "WebMercatorQuad") ||
      75        4729 :         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         722 :         poTMS->mTitle = "GoogleMapsCompatible";
      83         722 :         poTMS->mIdentifier = "GoogleMapsCompatible";
      84         722 :         poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
      85         722 :         poTMS->mBbox.mCrs = poTMS->mCrs;
      86         722 :         poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
      87         722 :         poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
      88         722 :         poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
      89         722 :         poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
      90         722 :         poTMS->mWellKnownScaleSet =
      91         722 :             "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible";
      92       23104 :         for (int i = 0; i <= 30; i++)
      93             :         {
      94       44764 :             TileMatrix tm;
      95       22382 :             tm.mId = CPLSPrintf("%d", i);
      96       22382 :             tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
      97       22382 :             tm.mResY = tm.mResX;
      98       22382 :             tm.mScaleDenominator = tm.mResX / 0.28e-3;
      99       22382 :             tm.mTopLeftX = -HALF_CIRCUMFERENCE;
     100       22382 :             tm.mTopLeftY = HALF_CIRCUMFERENCE;
     101       22382 :             tm.mTileWidth = 256;
     102       22382 :             tm.mTileHeight = 256;
     103       22382 :             tm.mMatrixWidth = 1 << i;
     104       22382 :             tm.mMatrixHeight = 1 << i;
     105       22382 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     106             :         }
     107         722 :         return poTMS;
     108             :     }
     109             : 
     110        4729 :     if (EQUAL(fileOrDef, "WorldMercatorWGS84Quad") ||
     111        4150 :         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         579 :         poTMS->mTitle = "WorldMercatorWGS84Quad";
     116         579 :         poTMS->mIdentifier = "WorldMercatorWGS84Quad";
     117         579 :         poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3395";
     118         579 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     119         579 :         poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
     120         579 :         poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
     121         579 :         poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
     122         579 :         poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
     123         579 :         poTMS->mWellKnownScaleSet =
     124         579 :             "http://www.opengis.net/def/wkss/OGC/1.0/WorldMercatorWGS84Quad";
     125       18528 :         for (int i = 0; i <= 30; i++)
     126             :         {
     127       35898 :             TileMatrix tm;
     128       17949 :             tm.mId = CPLSPrintf("%d", i);
     129       17949 :             tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
     130       17949 :             tm.mResY = tm.mResX;
     131       17949 :             tm.mScaleDenominator = tm.mResX / 0.28e-3;
     132       17949 :             tm.mTopLeftX = -HALF_CIRCUMFERENCE;
     133       17949 :             tm.mTopLeftY = HALF_CIRCUMFERENCE;
     134       17949 :             tm.mTileWidth = 256;
     135       17949 :             tm.mTileHeight = 256;
     136       17949 :             tm.mMatrixWidth = 1 << i;
     137       17949 :             tm.mMatrixHeight = 1 << i;
     138       17949 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     139             :         }
     140         579 :         return poTMS;
     141             :     }
     142             : 
     143        4150 :     if (EQUAL(fileOrDef, "PseudoTMS_GlobalMercator"))
     144             :     {
     145             :         /* See global-mercator at
     146             :            http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
     147         579 :         poTMS->mTitle = "PseudoTMS_GlobalMercator";
     148         579 :         poTMS->mIdentifier = "PseudoTMS_GlobalMercator";
     149         579 :         poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
     150         579 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     151         579 :         poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
     152         579 :         poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
     153         579 :         poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
     154         579 :         poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
     155       17949 :         for (int i = 0; i <= 29; i++)
     156             :         {
     157       34740 :             TileMatrix tm;
     158       17370 :             tm.mId = CPLSPrintf("%d", i);
     159       17370 :             tm.mResX = HALF_CIRCUMFERENCE / 256 / (1 << i);
     160       17370 :             tm.mResY = tm.mResX;
     161       17370 :             tm.mScaleDenominator = tm.mResX / 0.28e-3;
     162       17370 :             tm.mTopLeftX = -HALF_CIRCUMFERENCE;
     163       17370 :             tm.mTopLeftY = HALF_CIRCUMFERENCE;
     164       17370 :             tm.mTileWidth = 256;
     165       17370 :             tm.mTileHeight = 256;
     166       17370 :             tm.mMatrixWidth = 2 * (1 << i);
     167       17370 :             tm.mMatrixHeight = 2 * (1 << i);
     168       17370 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     169             :         }
     170         579 :         return poTMS;
     171             :     }
     172             : 
     173        3571 :     if (EQUAL(fileOrDef, "InspireCRS84Quad") ||
     174        3565 :         EQUAL(fileOrDef, "PseudoTMS_GlobalGeodetic") ||
     175        3565 :         EQUAL(fileOrDef, "WorldCRS84Quad") ||
     176        2984 :         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         587 :         poTMS->mTitle = "WorldCRS84Quad";
     188         587 :         poTMS->mIdentifier = "WorldCRS84Quad";
     189         587 :         poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
     190         587 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     191         587 :         poTMS->mBbox.mLowerCornerX = -180;
     192         587 :         poTMS->mBbox.mLowerCornerY = -90;
     193         587 :         poTMS->mBbox.mUpperCornerX = 180;
     194         587 :         poTMS->mBbox.mUpperCornerY = 90;
     195         587 :         poTMS->mWellKnownScaleSet =
     196         587 :             "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       18197 :         for (int i = 0; i <= 29; i++)
     200             :         {
     201       35220 :             TileMatrix tm;
     202       17610 :             tm.mId = CPLSPrintf("%d", i);
     203       17610 :             tm.mResX = 180. / 256 / (1 << i);
     204       17610 :             tm.mResY = tm.mResX;
     205       17610 :             tm.mScaleDenominator =
     206       17610 :                 tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
     207       17610 :             tm.mTopLeftX = -180;
     208       17610 :             tm.mTopLeftY = 90;
     209       17610 :             tm.mTileWidth = 256;
     210       17610 :             tm.mTileHeight = 256;
     211       17610 :             tm.mMatrixWidth = 2 * (1 << i);
     212       17610 :             tm.mMatrixHeight = 1 << i;
     213       17610 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     214             :         }
     215         587 :         return poTMS;
     216             :     }
     217             : 
     218        2984 :     if (EQUAL(fileOrDef, "GoogleCRS84Quad") ||
     219        2387 :         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         597 :         poTMS->mTitle = "GoogleCRS84Quad";
     225         597 :         poTMS->mIdentifier = "GoogleCRS84Quad";
     226         597 :         poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
     227         597 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     228         597 :         poTMS->mBbox.mLowerCornerX = -180;
     229         597 :         poTMS->mBbox.mLowerCornerY = -90;
     230         597 :         poTMS->mBbox.mUpperCornerX = 180;
     231         597 :         poTMS->mBbox.mUpperCornerY = 90;
     232         597 :         poTMS->mWellKnownScaleSet =
     233         597 :             "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad";
     234       19104 :         for (int i = 0; i <= 30; i++)
     235             :         {
     236       37014 :             TileMatrix tm;
     237       18507 :             tm.mId = CPLSPrintf("%d", i);
     238       18507 :             tm.mResX = 360. / 256 / (1 << i);
     239       18507 :             tm.mResY = tm.mResX;
     240       18507 :             tm.mScaleDenominator =
     241       18507 :                 tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
     242       18507 :             tm.mTopLeftX = -180;
     243       18507 :             tm.mTopLeftY = 180;
     244       18507 :             tm.mTileWidth = 256;
     245       18507 :             tm.mTileHeight = 256;
     246       18507 :             tm.mMatrixWidth = 1 << i;
     247       18507 :             tm.mMatrixHeight = 1 << i;
     248       18507 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     249             :         }
     250         597 :         return poTMS;
     251             :     }
     252             : 
     253        2387 :     bool loadOk = false;
     254        2387 :     if (  // TMS 2.0 spec
     255        2387 :         (strstr(fileOrDef, "\"crs\"") &&
     256          45 :          strstr(fileOrDef, "\"tileMatrices\"")) ||
     257             :         // TMS 1.0 spec
     258        2365 :         (strstr(fileOrDef, "\"type\"") &&
     259          31 :          strstr(fileOrDef, "\"TileMatrixSetType\"")) ||
     260        2334 :         (strstr(fileOrDef, "\"identifier\"") &&
     261           1 :          strstr(fileOrDef, "\"boundingBox\"") &&
     262           1 :          strstr(fileOrDef, "\"tileMatrix\"")))
     263             :     {
     264          54 :         loadOk = oDoc.LoadMemory(fileOrDef);
     265             :     }
     266        2333 :     else if (STARTS_WITH_CI(fileOrDef, "http://") ||
     267        2332 :              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        2332 :         if (VSIStatL(fileOrDef, &sStat) == 0)
     276             :         {
     277           1 :             loadOk = oDoc.Load(fileOrDef);
     278             :         }
     279             :         else
     280             :         {
     281        2331 :             const char *pszFilename = CPLFindFile(
     282        4662 :                 "gdal", (std::string("tms_") + fileOrDef + ".json").c_str());
     283        2331 :             if (pszFilename)
     284             :             {
     285        2329 :                 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        2387 :     if (!loadOk)
     295             :     {
     296           4 :         return nullptr;
     297             :     }
     298             : 
     299        4766 :     auto oRoot = oDoc.GetRoot();
     300             :     const bool bIsTMSv2 =
     301        2383 :         oRoot.GetObj("crs").IsValid() && oRoot.GetObj("tileMatrices").IsValid();
     302             : 
     303        4767 :     if (!bIsTMSv2 && oRoot.GetString("type") != "TileMatrixSetType" &&
     304        2384 :         !oRoot.GetObj("tileMatrix").IsValid())
     305             :     {
     306           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     307             :                  "Expected type = TileMatrixSetType");
     308           0 :         return nullptr;
     309             :     }
     310             : 
     311        2992 :     const auto GetCRS = [](const CPLJSONObject &j)
     312             :     {
     313        2992 :         if (j.IsValid())
     314             :         {
     315        2991 :             if (j.GetType() == CPLJSONObject::Type::String)
     316        2988 :                 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        2383 :     poTMS->mIdentifier = oRoot.GetString(bIsTMSv2 ? "id" : "identifier");
     345        2383 :     poTMS->mTitle = oRoot.GetString("title");
     346        2383 :     poTMS->mAbstract = oRoot.GetString(bIsTMSv2 ? "description" : "abstract");
     347        7149 :     const auto oBbox = oRoot.GetObj("boundingBox");
     348        2383 :     if (oBbox.IsValid())
     349             :     {
     350         609 :         poTMS->mBbox.mCrs = GetCRS(oBbox.GetObj("crs"));
     351        1827 :         const auto oLowerCorner = oBbox.GetArray("lowerCorner");
     352         609 :         if (oLowerCorner.IsValid() && oLowerCorner.Size() == 2)
     353             :         {
     354         609 :             poTMS->mBbox.mLowerCornerX = oLowerCorner[0].ToDouble(NaN);
     355         609 :             poTMS->mBbox.mLowerCornerY = oLowerCorner[1].ToDouble(NaN);
     356             :         }
     357        1827 :         const auto oUpperCorner = oBbox.GetArray("upperCorner");
     358         609 :         if (oUpperCorner.IsValid() && oUpperCorner.Size() == 2)
     359             :         {
     360         609 :             poTMS->mBbox.mUpperCornerX = oUpperCorner[0].ToDouble(NaN);
     361         609 :             poTMS->mBbox.mUpperCornerY = oUpperCorner[1].ToDouble(NaN);
     362             :         }
     363             :     }
     364        2383 :     poTMS->mCrs = GetCRS(oRoot.GetObj(bIsTMSv2 ? "crs" : "supportedCRS"));
     365        2383 :     poTMS->mWellKnownScaleSet = oRoot.GetString("wellKnownScaleSet");
     366             : 
     367        4766 :     OGRSpatialReference oCrs;
     368        2383 :     if (oCrs.SetFromUserInput(
     369        2383 :             poTMS->mCrs.c_str(),
     370        2383 :             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        2382 :     double dfMetersPerUnit = 1.0;
     378        2382 :     if (oCrs.IsProjected())
     379             :     {
     380        2336 :         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        7146 :         oRoot.GetArray(bIsTMSv2 ? "tileMatrices" : "tileMatrix");
     389        2382 :     if (oTileMatrices.IsValid())
     390             :     {
     391        2382 :         double dfLastScaleDenominator = std::numeric_limits<double>::max();
     392       47779 :         for (const auto &oTM : oTileMatrices)
     393             :         {
     394       45403 :             TileMatrix tm;
     395       45403 :             tm.mId = oTM.GetString(bIsTMSv2 ? "id" : "identifier");
     396       45403 :             tm.mScaleDenominator = oTM.GetDouble("scaleDenominator");
     397       45403 :             if (tm.mScaleDenominator >= dfLastScaleDenominator ||
     398       45403 :                 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       45402 :             dfLastScaleDenominator = tm.mScaleDenominator;
     406             :             // See note g of Table 2 of
     407             :             // http://docs.opengeospatial.org/is/17-083r2/17-083r2.html
     408       45402 :             tm.mResX = tm.mScaleDenominator * 0.28e-3 / dfMetersPerUnit;
     409       45402 :             tm.mResY = tm.mResX;
     410       45402 :             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       90804 :                 oTM.GetArray(bIsTMSv2 ? "pointOfOrigin" : "topLeftCorner");
     422       45402 :             if (oTopLeftCorner.IsValid() && oTopLeftCorner.Size() == 2)
     423             :             {
     424       45402 :                 tm.mTopLeftX = oTopLeftCorner[0].ToDouble(NaN);
     425       45402 :                 tm.mTopLeftY = oTopLeftCorner[1].ToDouble(NaN);
     426             :             }
     427       45402 :             tm.mTileWidth = oTM.GetInteger("tileWidth");
     428       45402 :             if (tm.mTileWidth <= 0)
     429             :             {
     430           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileWidth: %d",
     431             :                          tm.mTileWidth);
     432           1 :                 return nullptr;
     433             :             }
     434       45401 :             tm.mTileHeight = oTM.GetInteger("tileHeight");
     435       45401 :             if (tm.mTileHeight <= 0)
     436             :             {
     437           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileHeight: %d",
     438             :                          tm.mTileHeight);
     439           1 :                 return nullptr;
     440             :             }
     441       45400 :             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       45399 :             tm.mMatrixWidth = oTM.GetInteger("matrixWidth");
     450       45399 :             if (tm.mMatrixWidth <= 0)
     451             :             {
     452           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid matrixWidth: %d",
     453             :                          tm.mMatrixWidth);
     454           1 :                 return nullptr;
     455             :             }
     456       45398 :             tm.mMatrixHeight = oTM.GetInteger("matrixHeight");
     457       45398 :             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      136191 :                 bIsTMSv2 ? "variableMatrixWidths" : "variableMatrixWidth");
     466       45397 :             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       45397 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     479             :         }
     480             :     }
     481        2376 :     if (poTMS->mTileMatrixList.empty())
     482             :     {
     483           0 :         CPLError(CE_Failure, CPLE_AppDefined, "No tileMatrix defined");
     484           0 :         return nullptr;
     485             :     }
     486             : 
     487        2376 :     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        4902 : bool TileMatrixSet::hasVariableMatrixWidth() const
     548             : {
     549      132790 :     for (const auto &oTM : mTileMatrixList)
     550             :     {
     551      127890 :         if (!oTM.mVariableMatrixWidthList.empty())
     552             :         {
     553           2 :             return true;
     554             :         }
     555             :     }
     556        4900 :     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             : }  // namespace gdal
     603             : 
     604             : //! @endcond

Generated by: LCOV version 1.14