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

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  Class to handle TileMatrixSet
       5             :  * Author:   Even Rouault, <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2020, Even Rouault <even dot rouault at spatialys.com>
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_json.h"
      14             : #include "gdal_priv.h"
      15             : #include "ogr_spatialref.h"
      16             : 
      17             : #include <algorithm>
      18             : #include <cmath>
      19             : #include <cfloat>
      20             : #include <limits>
      21             : 
      22             : #include "tilematrixset.hpp"
      23             : 
      24             : //! @cond Doxygen_Suppress
      25             : 
      26             : namespace gdal
      27             : {
      28             : 
      29             : /************************************************************************/
      30             : /*                    listPredefinedTileMatrixSets()                    */
      31             : /************************************************************************/
      32             : 
      33             : std::vector<std::string>
      34         754 : TileMatrixSet::listPredefinedTileMatrixSets(bool includeHidden)
      35             : {
      36             :     std::vector<std::string> l{"GoogleMapsCompatible", "WorldCRS84Quad",
      37             :                                "WorldMercatorWGS84Quad", "GoogleCRS84Quad",
      38        6032 :                                "PseudoTMS_GlobalMercator"};
      39         754 :     if (includeHidden)
      40         292 :         l.push_back("GlobalGeodeticOriginLat270");
      41         754 :     const char *pszSomeFile = CPLFindFile("gdal", "tms_NZTM2000.json");
      42         754 :     if (pszSomeFile)
      43             :     {
      44        1508 :         std::set<std::string> set;
      45             :         CPLStringList aosList(
      46        1508 :             VSIReadDir(CPLGetDirnameSafe(pszSomeFile).c_str()));
      47      123656 :         for (int i = 0; i < aosList.size(); i++)
      48             :         {
      49      122902 :             const size_t nLen = strlen(aosList[i]);
      50      242788 :             if (nLen > strlen("tms_") + strlen(".json") &&
      51      125918 :                 STARTS_WITH(aosList[i], "tms_") &&
      52        3016 :                 EQUAL(aosList[i] + nLen - strlen(".json"), ".json"))
      53             :             {
      54        6032 :                 std::string id(aosList[i] + strlen("tms_"),
      55        6032 :                                nLen - (strlen("tms_") + strlen(".json")));
      56        3016 :                 set.insert(std::move(id));
      57             :             }
      58             :         }
      59        3770 :         for (const std::string &id : set)
      60        3016 :             l.push_back(id);
      61             :     }
      62         754 :     return l;
      63             : }
      64             : 
      65             : /************************************************************************/
      66             : /*                               parse()                                */
      67             : /************************************************************************/
      68             : 
      69        7404 : std::unique_ptr<TileMatrixSet> TileMatrixSet::parse(const char *fileOrDef)
      70             : {
      71       14808 :     CPLJSONDocument oDoc;
      72       14808 :     std::unique_ptr<TileMatrixSet> poTMS(new TileMatrixSet());
      73             : 
      74        7404 :     constexpr double HALF_CIRCUMFERENCE = 6378137 * M_PI;
      75             : 
      76        7404 :     if (EQUAL(fileOrDef, "GoogleMapsCompatible") ||
      77        6417 :         EQUAL(fileOrDef, "WebMercatorQuad") ||
      78        6417 :         EQUAL(
      79             :             fileOrDef,
      80             :             "http://www.opengis.net/def/tilematrixset/OGC/1.0/WebMercatorQuad"))
      81             :     {
      82             :         /* See http://portal.opengeospatial.org/files/?artifact_id=35326
      83             :          * (WMTS 1.0), Annex E.4 */
      84             :         // or https://docs.ogc.org/is/17-083r4/17-083r4.html#toc49
      85         987 :         poTMS->mTitle = "GoogleMapsCompatible";
      86         987 :         poTMS->mIdentifier = "GoogleMapsCompatible";
      87         987 :         poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
      88         987 :         poTMS->mBbox.mCrs = poTMS->mCrs;
      89         987 :         poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
      90         987 :         poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
      91         987 :         poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
      92         987 :         poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
      93         987 :         poTMS->mWellKnownScaleSet =
      94         987 :             "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible";
      95       31584 :         for (int i = 0; i <= 30; i++)
      96             :         {
      97       61194 :             TileMatrix tm;
      98       30597 :             tm.mId = CPLSPrintf("%d", i);
      99       30597 :             tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
     100       30597 :             tm.mResY = tm.mResX;
     101       30597 :             tm.mScaleDenominator = tm.mResX / 0.28e-3;
     102       30597 :             tm.mTopLeftX = -HALF_CIRCUMFERENCE;
     103       30597 :             tm.mTopLeftY = HALF_CIRCUMFERENCE;
     104       30597 :             tm.mTileWidth = 256;
     105       30597 :             tm.mTileHeight = 256;
     106       30597 :             tm.mMatrixWidth = 1 << i;
     107       30597 :             tm.mMatrixHeight = 1 << i;
     108       30597 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     109             :         }
     110         987 :         return poTMS;
     111             :     }
     112             : 
     113        6417 :     if (EQUAL(fileOrDef, "WorldMercatorWGS84Quad") ||
     114        5664 :         EQUAL(fileOrDef, "http://www.opengis.net/def/tilematrixset/OGC/1.0/"
     115             :                          "WorldMercatorWGS84Quad"))
     116             :     {
     117             :         // See https://docs.ogc.org/is/17-083r4/17-083r4.html#toc51
     118         753 :         poTMS->mTitle = "WorldMercatorWGS84Quad";
     119         753 :         poTMS->mIdentifier = "WorldMercatorWGS84Quad";
     120         753 :         poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3395";
     121         753 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     122         753 :         poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
     123         753 :         poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
     124         753 :         poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
     125         753 :         poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
     126         753 :         poTMS->mWellKnownScaleSet =
     127         753 :             "http://www.opengis.net/def/wkss/OGC/1.0/WorldMercatorWGS84Quad";
     128       24096 :         for (int i = 0; i <= 30; i++)
     129             :         {
     130       46686 :             TileMatrix tm;
     131       23343 :             tm.mId = CPLSPrintf("%d", i);
     132       23343 :             tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
     133       23343 :             tm.mResY = tm.mResX;
     134       23343 :             tm.mScaleDenominator = tm.mResX / 0.28e-3;
     135       23343 :             tm.mTopLeftX = -HALF_CIRCUMFERENCE;
     136       23343 :             tm.mTopLeftY = HALF_CIRCUMFERENCE;
     137       23343 :             tm.mTileWidth = 256;
     138       23343 :             tm.mTileHeight = 256;
     139       23343 :             tm.mMatrixWidth = 1 << i;
     140       23343 :             tm.mMatrixHeight = 1 << i;
     141       23343 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     142             :         }
     143         753 :         return poTMS;
     144             :     }
     145             : 
     146        5664 :     if (EQUAL(fileOrDef, "PseudoTMS_GlobalMercator"))
     147             :     {
     148             :         /* See global-mercator at
     149             :            http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
     150         753 :         poTMS->mTitle = "PseudoTMS_GlobalMercator";
     151         753 :         poTMS->mIdentifier = "PseudoTMS_GlobalMercator";
     152         753 :         poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
     153         753 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     154         753 :         poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
     155         753 :         poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
     156         753 :         poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
     157         753 :         poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
     158       23343 :         for (int i = 0; i <= 29; i++)
     159             :         {
     160       45180 :             TileMatrix tm;
     161       22590 :             tm.mId = CPLSPrintf("%d", i);
     162       22590 :             tm.mResX = HALF_CIRCUMFERENCE / 256 / (1 << i);
     163       22590 :             tm.mResY = tm.mResX;
     164       22590 :             tm.mScaleDenominator = tm.mResX / 0.28e-3;
     165       22590 :             tm.mTopLeftX = -HALF_CIRCUMFERENCE;
     166       22590 :             tm.mTopLeftY = HALF_CIRCUMFERENCE;
     167       22590 :             tm.mTileWidth = 256;
     168       22590 :             tm.mTileHeight = 256;
     169       22590 :             tm.mMatrixWidth = 2 * (1 << i);
     170       22590 :             tm.mMatrixHeight = 2 * (1 << i);
     171       22590 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     172             :         }
     173         753 :         return poTMS;
     174             :     }
     175             : 
     176        4911 :     if (EQUAL(fileOrDef, "InspireCRS84Quad") ||
     177        4905 :         EQUAL(fileOrDef, "PseudoTMS_GlobalGeodetic") ||
     178        4905 :         EQUAL(fileOrDef, "WorldCRS84Quad") ||
     179        4149 :         EQUAL(
     180             :             fileOrDef,
     181             :             "http://www.opengis.net/def/tilematrixset/OGC/1.0/WorldCRS84Quad"))
     182             :     {
     183             :         /* See InspireCRS84Quad at
     184             :          * http://inspire.ec.europa.eu/documents/Network_Services/TechnicalGuidance_ViewServices_v3.0.pdf
     185             :          */
     186             :         /* This is exactly the same as PseudoTMS_GlobalGeodetic */
     187             :         /* See global-geodetic at
     188             :          * http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
     189             :         // See also http://docs.opengeospatial.org/is/17-083r2/17-083r2.html#76
     190         762 :         poTMS->mTitle = "WorldCRS84Quad";
     191         762 :         poTMS->mIdentifier = "WorldCRS84Quad";
     192         762 :         poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
     193         762 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     194         762 :         poTMS->mBbox.mLowerCornerX = -180;
     195         762 :         poTMS->mBbox.mLowerCornerY = -90;
     196         762 :         poTMS->mBbox.mUpperCornerX = 180;
     197         762 :         poTMS->mBbox.mUpperCornerY = 90;
     198         762 :         poTMS->mWellKnownScaleSet =
     199         762 :             "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad";
     200             :         // Limit to zoom level 29, because at that zoom level nMatrixWidth = 2 * (1 << 29) = 1073741824
     201             :         // and at 30 it would overflow int32.
     202       23622 :         for (int i = 0; i <= 29; i++)
     203             :         {
     204       45720 :             TileMatrix tm;
     205       22860 :             tm.mId = CPLSPrintf("%d", i);
     206       22860 :             tm.mResX = 180. / 256 / (1 << i);
     207       22860 :             tm.mResY = tm.mResX;
     208       22860 :             tm.mScaleDenominator =
     209       22860 :                 tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
     210       22860 :             tm.mTopLeftX = -180;
     211       22860 :             tm.mTopLeftY = 90;
     212       22860 :             tm.mTileWidth = 256;
     213       22860 :             tm.mTileHeight = 256;
     214       22860 :             tm.mMatrixWidth = 2 * (1 << i);
     215       22860 :             tm.mMatrixHeight = 1 << i;
     216       22860 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     217             :         }
     218         762 :         return poTMS;
     219             :     }
     220             : 
     221        4149 :     if (EQUAL(fileOrDef, "GoogleCRS84Quad") ||
     222        3378 :         EQUAL(fileOrDef,
     223             :               "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad"))
     224             :     {
     225             :         /* See http://portal.opengeospatial.org/files/?artifact_id=35326 (WMTS 1.0),
     226             :                Annex E.3 */
     227         771 :         poTMS->mTitle = "GoogleCRS84Quad";
     228         771 :         poTMS->mIdentifier = "GoogleCRS84Quad";
     229         771 :         poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
     230         771 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     231         771 :         poTMS->mBbox.mLowerCornerX = -180;
     232         771 :         poTMS->mBbox.mLowerCornerY = -90;
     233         771 :         poTMS->mBbox.mUpperCornerX = 180;
     234         771 :         poTMS->mBbox.mUpperCornerY = 90;
     235         771 :         poTMS->mWellKnownScaleSet =
     236         771 :             "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad";
     237       24672 :         for (int i = 0; i <= 30; i++)
     238             :         {
     239       47802 :             TileMatrix tm;
     240       23901 :             tm.mId = CPLSPrintf("%d", i);
     241       23901 :             tm.mResX = 360. / 256 / (1 << i);
     242       23901 :             tm.mResY = tm.mResX;
     243       23901 :             tm.mScaleDenominator =
     244       23901 :                 tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
     245       23901 :             tm.mTopLeftX = -180;
     246       23901 :             tm.mTopLeftY = 180;
     247       23901 :             tm.mTileWidth = 256;
     248       23901 :             tm.mTileHeight = 256;
     249       23901 :             tm.mMatrixWidth = 1 << i;
     250       23901 :             tm.mMatrixHeight = 1 << i;
     251       23901 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     252             :         }
     253         771 :         return poTMS;
     254             :     }
     255             : 
     256        3378 :     if (EQUAL(fileOrDef, "GlobalGeodeticOriginLat270"))
     257             :     {
     258             :         // gdal2tiles --profile=geodetic *without* --tmscompatible
     259         292 :         poTMS->mTitle = "GlobalGeodeticOriginLat270";
     260         292 :         poTMS->mIdentifier = "GlobalGeodeticOriginLat270";
     261         292 :         poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
     262         292 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     263         292 :         poTMS->mBbox.mLowerCornerX = -180;
     264         292 :         poTMS->mBbox.mLowerCornerY = -90;
     265         292 :         poTMS->mBbox.mUpperCornerX = 180;
     266         292 :         poTMS->mBbox.mUpperCornerY = 270;
     267        9344 :         for (int i = 0; i <= 30; i++)
     268             :         {
     269       18104 :             TileMatrix tm;
     270        9052 :             tm.mId = CPLSPrintf("%d", i);
     271        9052 :             tm.mResX = 360. / 256 / (1 << i);
     272        9052 :             tm.mResY = tm.mResX;
     273        9052 :             tm.mScaleDenominator =
     274        9052 :                 tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
     275        9052 :             tm.mTopLeftX = -180;
     276        9052 :             tm.mTopLeftY = 270;
     277        9052 :             tm.mTileWidth = 256;
     278        9052 :             tm.mTileHeight = 256;
     279        9052 :             tm.mMatrixWidth = 1 << i;
     280        9052 :             tm.mMatrixHeight = 1 << i;
     281        9052 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     282             :         }
     283         292 :         return poTMS;
     284             :     }
     285             : 
     286        3086 :     bool loadOk = false;
     287        3086 :     if (  // TMS 2.0 spec
     288        3086 :         (strstr(fileOrDef, "\"crs\"") &&
     289          47 :          strstr(fileOrDef, "\"tileMatrices\"")) ||
     290             :         // TMS 1.0 spec
     291        3064 :         (strstr(fileOrDef, "\"type\"") &&
     292          33 :          strstr(fileOrDef, "\"TileMatrixSetType\"")) ||
     293        3031 :         (strstr(fileOrDef, "\"identifier\"") &&
     294           1 :          strstr(fileOrDef, "\"boundingBox\"") &&
     295           1 :          strstr(fileOrDef, "\"tileMatrix\"")))
     296             :     {
     297          56 :         loadOk = oDoc.LoadMemory(fileOrDef);
     298             :     }
     299        3030 :     else if (STARTS_WITH_CI(fileOrDef, "http://") ||
     300        3029 :              STARTS_WITH_CI(fileOrDef, "https://"))
     301             :     {
     302           1 :         const char *const apszOptions[] = {"MAX_FILE_SIZE=1000000", nullptr};
     303           1 :         loadOk = oDoc.LoadUrl(fileOrDef, apszOptions);
     304             :     }
     305             :     else
     306             :     {
     307             :         VSIStatBufL sStat;
     308        3029 :         if (VSIStatL(fileOrDef, &sStat) == 0)
     309             :         {
     310           1 :             loadOk = oDoc.Load(fileOrDef);
     311             :         }
     312             :         else
     313             :         {
     314        3028 :             const char *pszFilename = CPLFindFile(
     315        6056 :                 "gdal", (std::string("tms_") + fileOrDef + ".json").c_str());
     316        3028 :             if (pszFilename)
     317             :             {
     318        3026 :                 loadOk = oDoc.Load(pszFilename);
     319             :             }
     320             :             else
     321             :             {
     322           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
     323             :                          "Invalid tiling matrix set name");
     324             :             }
     325             :         }
     326             :     }
     327        3086 :     if (!loadOk)
     328             :     {
     329           4 :         return nullptr;
     330             :     }
     331             : 
     332        6164 :     auto oRoot = oDoc.GetRoot();
     333             :     const bool bIsTMSv2 =
     334        3082 :         oRoot.GetObj("crs").IsValid() && oRoot.GetObj("tileMatrices").IsValid();
     335             : 
     336        6165 :     if (!bIsTMSv2 && oRoot.GetString("type") != "TileMatrixSetType" &&
     337        3083 :         !oRoot.GetObj("tileMatrix").IsValid())
     338             :     {
     339           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     340             :                  "Expected type = TileMatrixSetType");
     341           0 :         return nullptr;
     342             :     }
     343             : 
     344        3867 :     const auto GetCRS = [](const CPLJSONObject &j)
     345             :     {
     346        3867 :         if (j.IsValid())
     347             :         {
     348        3866 :             if (j.GetType() == CPLJSONObject::Type::String)
     349        3863 :                 return j.ToString();
     350             : 
     351           3 :             else if (j.GetType() == CPLJSONObject::Type::Object)
     352             :             {
     353           6 :                 std::string osURI = j.GetString("uri");
     354           3 :                 if (!osURI.empty())
     355           1 :                     return osURI;
     356             : 
     357             :                 // Quite a bit of confusion around wkt.
     358             :                 // See https://github.com/opengeospatial/ogcapi-tiles/issues/170
     359           4 :                 const auto jWKT = j.GetObj("wkt");
     360           2 :                 if (jWKT.GetType() == CPLJSONObject::Type::String)
     361             :                 {
     362           2 :                     std::string osWKT = jWKT.ToString();
     363           1 :                     if (!osWKT.empty())
     364           1 :                         return osWKT;
     365             :                 }
     366           1 :                 else if (jWKT.GetType() == CPLJSONObject::Type::Object)
     367             :                 {
     368           2 :                     std::string osWKT = jWKT.ToString();
     369           1 :                     if (!osWKT.empty())
     370           1 :                         return osWKT;
     371             :                 }
     372             :             }
     373             :         }
     374           1 :         return std::string();
     375             :     };
     376             : 
     377        3082 :     poTMS->mIdentifier = oRoot.GetString(bIsTMSv2 ? "id" : "identifier");
     378        3082 :     poTMS->mTitle = oRoot.GetString("title");
     379        3082 :     poTMS->mAbstract = oRoot.GetString(bIsTMSv2 ? "description" : "abstract");
     380        9246 :     const auto oBbox = oRoot.GetObj("boundingBox");
     381        3082 :     if (oBbox.IsValid())
     382             :     {
     383         785 :         poTMS->mBbox.mCrs = GetCRS(oBbox.GetObj("crs"));
     384        2355 :         const auto oLowerCorner = oBbox.GetArray("lowerCorner");
     385         785 :         if (oLowerCorner.IsValid() && oLowerCorner.Size() == 2)
     386             :         {
     387         785 :             poTMS->mBbox.mLowerCornerX = oLowerCorner[0].ToDouble(NaN);
     388         785 :             poTMS->mBbox.mLowerCornerY = oLowerCorner[1].ToDouble(NaN);
     389             :         }
     390        2355 :         const auto oUpperCorner = oBbox.GetArray("upperCorner");
     391         785 :         if (oUpperCorner.IsValid() && oUpperCorner.Size() == 2)
     392             :         {
     393         785 :             poTMS->mBbox.mUpperCornerX = oUpperCorner[0].ToDouble(NaN);
     394         785 :             poTMS->mBbox.mUpperCornerY = oUpperCorner[1].ToDouble(NaN);
     395             :         }
     396             :     }
     397        3082 :     poTMS->mCrs = GetCRS(oRoot.GetObj(bIsTMSv2 ? "crs" : "supportedCRS"));
     398        3082 :     poTMS->mWellKnownScaleSet = oRoot.GetString("wellKnownScaleSet");
     399             : 
     400        6164 :     OGRSpatialReference oCrs;
     401        3082 :     if (oCrs.SetFromUserInput(
     402        3082 :             poTMS->mCrs.c_str(),
     403        3082 :             OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS) !=
     404             :         OGRERR_NONE)
     405             :     {
     406           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse CRS %s",
     407           1 :                  poTMS->mCrs.c_str());
     408           1 :         return nullptr;
     409             :     }
     410        3081 :     double dfMetersPerUnit = 1.0;
     411        3081 :     if (oCrs.IsProjected())
     412             :     {
     413        3035 :         dfMetersPerUnit = oCrs.GetLinearUnits();
     414             :     }
     415          46 :     else if (oCrs.IsGeographic())
     416             :     {
     417          46 :         dfMetersPerUnit = oCrs.GetSemiMajor() * M_PI / 180;
     418             :     }
     419             : 
     420             :     const auto oTileMatrices =
     421        9243 :         oRoot.GetArray(bIsTMSv2 ? "tileMatrices" : "tileMatrix");
     422        3081 :     if (oTileMatrices.IsValid())
     423             :     {
     424        3081 :         double dfLastScaleDenominator = std::numeric_limits<double>::max();
     425       61898 :         for (const auto &oTM : oTileMatrices)
     426             :         {
     427       58823 :             TileMatrix tm;
     428       58823 :             tm.mId = oTM.GetString(bIsTMSv2 ? "id" : "identifier");
     429       58823 :             tm.mScaleDenominator = oTM.GetDouble("scaleDenominator");
     430       58823 :             if (tm.mScaleDenominator >= dfLastScaleDenominator ||
     431       58823 :                 tm.mScaleDenominator <= 0)
     432             :             {
     433           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     434             :                          "Invalid scale denominator or non-decreasing series "
     435             :                          "of scale denominators");
     436           1 :                 return nullptr;
     437             :             }
     438       58822 :             dfLastScaleDenominator = tm.mScaleDenominator;
     439             :             // See note g of Table 2 of
     440             :             // http://docs.opengeospatial.org/is/17-083r2/17-083r2.html
     441       58822 :             tm.mResX = tm.mScaleDenominator * 0.28e-3 / dfMetersPerUnit;
     442       58822 :             tm.mResY = tm.mResX;
     443       58822 :             if (bIsTMSv2)
     444             :             {
     445        1635 :                 const auto osCornerOfOrigin = oTM.GetString("cornerOfOrigin");
     446         545 :                 if (!osCornerOfOrigin.empty() && osCornerOfOrigin != "topLeft")
     447             :                 {
     448           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     449             :                              "cornerOfOrigin = %s not supported",
     450             :                              osCornerOfOrigin.c_str());
     451             :                 }
     452             :             }
     453             :             const auto oTopLeftCorner =
     454      117644 :                 oTM.GetArray(bIsTMSv2 ? "pointOfOrigin" : "topLeftCorner");
     455       58822 :             if (oTopLeftCorner.IsValid() && oTopLeftCorner.Size() == 2)
     456             :             {
     457       58822 :                 tm.mTopLeftX = oTopLeftCorner[0].ToDouble(NaN);
     458       58822 :                 tm.mTopLeftY = oTopLeftCorner[1].ToDouble(NaN);
     459             :             }
     460       58822 :             tm.mTileWidth = oTM.GetInteger("tileWidth");
     461       58822 :             if (tm.mTileWidth <= 0)
     462             :             {
     463           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileWidth: %d",
     464             :                          tm.mTileWidth);
     465           1 :                 return nullptr;
     466             :             }
     467       58821 :             tm.mTileHeight = oTM.GetInteger("tileHeight");
     468       58821 :             if (tm.mTileHeight <= 0)
     469             :             {
     470           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileHeight: %d",
     471             :                          tm.mTileHeight);
     472           1 :                 return nullptr;
     473             :             }
     474       58820 :             if (tm.mTileWidth > INT_MAX / tm.mTileHeight)
     475             :             {
     476           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     477             :                          "tileWidth(%d) x tileHeight(%d) larger than "
     478             :                          "INT_MAX",
     479             :                          tm.mTileWidth, tm.mTileHeight);
     480           1 :                 return nullptr;
     481             :             }
     482       58819 :             tm.mMatrixWidth = oTM.GetInteger("matrixWidth");
     483       58819 :             if (tm.mMatrixWidth <= 0)
     484             :             {
     485           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid matrixWidth: %d",
     486             :                          tm.mMatrixWidth);
     487           1 :                 return nullptr;
     488             :             }
     489       58818 :             tm.mMatrixHeight = oTM.GetInteger("matrixHeight");
     490       58818 :             if (tm.mMatrixHeight <= 0)
     491             :             {
     492           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     493             :                          "Invalid matrixHeight: %d", tm.mMatrixHeight);
     494           1 :                 return nullptr;
     495             :             }
     496             : 
     497             :             const auto oVariableMatrixWidths = oTM.GetArray(
     498      176451 :                 bIsTMSv2 ? "variableMatrixWidths" : "variableMatrixWidth");
     499       58817 :             if (oVariableMatrixWidths.IsValid())
     500             :             {
     501           8 :                 for (const auto &oVMW : oVariableMatrixWidths)
     502             :                 {
     503           5 :                     TileMatrix::VariableMatrixWidth vmw;
     504           5 :                     vmw.mCoalesce = oVMW.GetInteger("coalesce");
     505           5 :                     vmw.mMinTileRow = oVMW.GetInteger("minTileRow");
     506           5 :                     vmw.mMaxTileRow = oVMW.GetInteger("maxTileRow");
     507           5 :                     tm.mVariableMatrixWidthList.emplace_back(std::move(vmw));
     508             :                 }
     509             :             }
     510             : 
     511       58817 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     512             :         }
     513             :     }
     514        3075 :     if (poTMS->mTileMatrixList.empty())
     515             :     {
     516           0 :         CPLError(CE_Failure, CPLE_AppDefined, "No tileMatrix defined");
     517           0 :         return nullptr;
     518             :     }
     519             : 
     520        3075 :     return poTMS;
     521             : }
     522             : 
     523             : /************************************************************************/
     524             : /*                      haveAllLevelsSameTopLeft()                      */
     525             : /************************************************************************/
     526             : 
     527        4253 : bool TileMatrixSet::haveAllLevelsSameTopLeft() const
     528             : {
     529      113149 :     for (const auto &oTM : mTileMatrixList)
     530             :     {
     531      217793 :         if (oTM.mTopLeftX != mTileMatrixList[0].mTopLeftX ||
     532      108896 :             oTM.mTopLeftY != mTileMatrixList[0].mTopLeftY)
     533             :         {
     534           1 :             return false;
     535             :         }
     536             :     }
     537        4252 :     return true;
     538             : }
     539             : 
     540             : /************************************************************************/
     541             : /*                     haveAllLevelsSameTileSize()                      */
     542             : /************************************************************************/
     543             : 
     544        4253 : bool TileMatrixSet::haveAllLevelsSameTileSize() const
     545             : {
     546      113149 :     for (const auto &oTM : mTileMatrixList)
     547             :     {
     548      217793 :         if (oTM.mTileWidth != mTileMatrixList[0].mTileWidth ||
     549      108896 :             oTM.mTileHeight != mTileMatrixList[0].mTileHeight)
     550             :         {
     551           1 :             return false;
     552             :         }
     553             :     }
     554        4252 :     return true;
     555             : }
     556             : 
     557             : /************************************************************************/
     558             : /*                   hasOnlyPowerOfTwoVaryingScales()                   */
     559             : /************************************************************************/
     560             : 
     561        2312 : bool TileMatrixSet::hasOnlyPowerOfTwoVaryingScales() const
     562             : {
     563       50657 :     for (size_t i = 1; i < mTileMatrixList.size(); i++)
     564             :     {
     565       97692 :         if (mTileMatrixList[i].mScaleDenominator == 0 ||
     566       48846 :             std::fabs(mTileMatrixList[i - 1].mScaleDenominator /
     567       48846 :                           mTileMatrixList[i].mScaleDenominator -
     568             :                       2) > 1e-10)
     569             :         {
     570         501 :             return false;
     571             :         }
     572             :     }
     573        1811 :     return true;
     574             : }
     575             : 
     576             : /************************************************************************/
     577             : /*                       hasVariableMatrixWidth()                       */
     578             : /************************************************************************/
     579             : 
     580        6855 : bool TileMatrixSet::hasVariableMatrixWidth() const
     581             : {
     582      186690 :     for (const auto &oTM : mTileMatrixList)
     583             :     {
     584      179837 :         if (!oTM.mVariableMatrixWidthList.empty())
     585             :         {
     586           2 :             return true;
     587             :         }
     588             :     }
     589        6853 :     return false;
     590             : }
     591             : 
     592             : /************************************************************************/
     593             : /*                            createRaster()                            */
     594             : /************************************************************************/
     595             : 
     596             : /* static */
     597             : std::unique_ptr<TileMatrixSet>
     598           6 : TileMatrixSet::createRaster(int width, int height, int tileSize,
     599             :                             int zoomLevelCount, double dfTopLeftX,
     600             :                             double dfTopLeftY, double dfResXFull,
     601             :                             double dfResYFull, const std::string &crs)
     602             : {
     603           6 :     CPLAssert(width > 0);
     604           6 :     CPLAssert(height > 0);
     605           6 :     CPLAssert(tileSize > 0);
     606           6 :     CPLAssert(zoomLevelCount > 0);
     607           6 :     std::unique_ptr<TileMatrixSet> poTMS(new TileMatrixSet());
     608           6 :     poTMS->mTitle = "raster";
     609           6 :     poTMS->mIdentifier = "raster";
     610           6 :     poTMS->mCrs = crs;
     611           6 :     poTMS->mBbox.mCrs = poTMS->mCrs;
     612           6 :     poTMS->mBbox.mLowerCornerX = dfTopLeftX;
     613           6 :     poTMS->mBbox.mLowerCornerY = dfTopLeftY - height * dfResYFull;
     614           6 :     poTMS->mBbox.mUpperCornerX = dfTopLeftX + width * dfResYFull;
     615           6 :     poTMS->mBbox.mUpperCornerY = dfTopLeftY;
     616          16 :     for (int i = 0; i < zoomLevelCount; i++)
     617             :     {
     618          20 :         TileMatrix tm;
     619          10 :         tm.mId = CPLSPrintf("%d", i);
     620          10 :         const int iRev = zoomLevelCount - 1 - i;
     621          10 :         tm.mResX = dfResXFull * (1 << iRev);
     622          10 :         tm.mResY = dfResYFull * (1 << iRev);
     623          10 :         tm.mScaleDenominator = tm.mResX / 0.28e-3;
     624          10 :         tm.mTopLeftX = poTMS->mBbox.mLowerCornerX;
     625          10 :         tm.mTopLeftY = poTMS->mBbox.mUpperCornerY;
     626          10 :         tm.mTileWidth = tileSize;
     627          10 :         tm.mTileHeight = tileSize;
     628          10 :         tm.mMatrixWidth = std::max(1, DIV_ROUND_UP(width >> iRev, tileSize));
     629          10 :         tm.mMatrixHeight = std::max(1, DIV_ROUND_UP(height >> iRev, tileSize));
     630          10 :         poTMS->mTileMatrixList.emplace_back(std::move(tm));
     631             :     }
     632           6 :     return poTMS;
     633             : }
     634             : 
     635             : /************************************************************************/
     636             : /*                         exportToTMSJsonV1()                          */
     637             : /************************************************************************/
     638             : 
     639          45 : std::string TileMatrixSet::exportToTMSJsonV1() const
     640             : {
     641          90 :     CPLJSONObject oRoot;
     642          45 :     oRoot["type"] = "TileMatrixSetType";
     643          45 :     oRoot["title"] = mTitle;
     644          45 :     oRoot["identifier"] = mIdentifier;
     645          45 :     if (!mAbstract.empty())
     646           0 :         oRoot["abstract"] = mAbstract;
     647          45 :     if (!std::isnan(mBbox.mLowerCornerX))
     648             :     {
     649          45 :         CPLJSONObject oBbox;
     650          45 :         oBbox["type"] = "BoundingBoxType";
     651          45 :         oBbox["crs"] = mBbox.mCrs;
     652          45 :         oBbox["lowerCorner"] = {mBbox.mLowerCornerX, mBbox.mLowerCornerY};
     653          45 :         oBbox["upperCorner"] = {mBbox.mUpperCornerX, mBbox.mUpperCornerY};
     654          45 :         oRoot["boundingBox"] = std::move(oBbox);
     655             :     }
     656          45 :     oRoot["supportedCRS"] = mCrs;
     657          45 :     if (!mWellKnownScaleSet.empty())
     658          40 :         oRoot["wellKnownScaleSet"] = mWellKnownScaleSet;
     659             : 
     660          45 :     CPLJSONArray oTileMatrices;
     661         133 :     for (const auto &tm : mTileMatrixList)
     662             :     {
     663         176 :         CPLJSONObject oTM;
     664          88 :         oTM["type"] = "TileMatrixType";
     665          88 :         oTM["identifier"] = tm.mId;
     666          88 :         oTM["scaleDenominator"] = tm.mScaleDenominator;
     667          88 :         oTM["topLeftCorner"] = {tm.mTopLeftX, tm.mTopLeftY};
     668          88 :         oTM["tileWidth"] = tm.mTileWidth;
     669          88 :         oTM["tileHeight"] = tm.mTileHeight;
     670          88 :         oTM["matrixWidth"] = tm.mMatrixWidth;
     671          88 :         oTM["matrixHeight"] = tm.mMatrixHeight;
     672             : 
     673          88 :         if (!tm.mVariableMatrixWidthList.empty())
     674             :         {
     675           1 :             CPLJSONArray oVariableMatrixWidths;
     676           2 :             for (const auto &vmw : tm.mVariableMatrixWidthList)
     677             :             {
     678           2 :                 CPLJSONObject oVMW;
     679           1 :                 oVMW["coalesce"] = vmw.mCoalesce;
     680           1 :                 oVMW["minTileRow"] = vmw.mMinTileRow;
     681           1 :                 oVMW["maxTileRow"] = vmw.mMaxTileRow;
     682           1 :                 oVariableMatrixWidths.Add(oVMW);
     683             :             }
     684           1 :             oTM["variableMatrixWidth"] = oVariableMatrixWidths;
     685             :         }
     686             : 
     687          88 :         oTileMatrices.Add(oTM);
     688             :     }
     689          45 :     oRoot["tileMatrix"] = oTileMatrices;
     690          90 :     return CPLString(oRoot.Format(CPLJSONObject::PrettyFormat::Pretty))
     691          90 :         .replaceAll("\\/", '/');
     692             : }
     693             : 
     694             : }  // namespace gdal
     695             : 
     696             : //! @endcond

Generated by: LCOV version 1.14