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-05-23 01:39:23 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         779 : TileMatrixSet::listPredefinedTileMatrixSets(bool includeHidden)
      35             : {
      36             :     std::vector<std::string> l{"GoogleMapsCompatible", "WorldCRS84Quad",
      37             :                                "WorldMercatorWGS84Quad", "GoogleCRS84Quad",
      38        6232 :                                "PseudoTMS_GlobalMercator"};
      39         779 :     if (includeHidden)
      40         317 :         l.push_back("GlobalGeodeticOriginLat270");
      41         779 :     const char *pszSomeFile = CPLFindFile("gdal", "tms_NZTM2000.json");
      42         779 :     if (pszSomeFile)
      43             :     {
      44        1558 :         std::set<std::string> set;
      45             :         CPLStringList aosList(
      46        1558 :             VSIReadDir(CPLGetDirnameSafe(pszSomeFile).c_str()));
      47      127756 :         for (int i = 0; i < aosList.size(); i++)
      48             :         {
      49      126977 :             const size_t nLen = strlen(aosList[i]);
      50      250838 :             if (nLen > strlen("tms_") + strlen(".json") &&
      51      130093 :                 STARTS_WITH(aosList[i], "tms_") &&
      52        3116 :                 EQUAL(aosList[i] + nLen - strlen(".json"), ".json"))
      53             :             {
      54        6232 :                 std::string id(aosList[i] + strlen("tms_"),
      55        6232 :                                nLen - (strlen("tms_") + strlen(".json")));
      56        3116 :                 set.insert(std::move(id));
      57             :             }
      58             :         }
      59        3895 :         for (const std::string &id : set)
      60        3116 :             l.push_back(id);
      61             :     }
      62         779 :     return l;
      63             : }
      64             : 
      65             : /************************************************************************/
      66             : /*                               parse()                                */
      67             : /************************************************************************/
      68             : 
      69        7668 : std::unique_ptr<TileMatrixSet> TileMatrixSet::parse(const char *fileOrDef)
      70             : {
      71       15336 :     CPLJSONDocument oDoc;
      72       15336 :     std::unique_ptr<TileMatrixSet> poTMS(new TileMatrixSet());
      73             : 
      74        7668 :     constexpr double HALF_CIRCUMFERENCE = 6378137 * M_PI;
      75             : 
      76        7668 :     if (EQUAL(fileOrDef, "GoogleMapsCompatible") ||
      77        6642 :         EQUAL(fileOrDef, "WebMercatorQuad") ||
      78        6642 :         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        1026 :         poTMS->mTitle = "GoogleMapsCompatible";
      86        1026 :         poTMS->mIdentifier = "GoogleMapsCompatible";
      87        1026 :         poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
      88        1026 :         poTMS->mBbox.mCrs = poTMS->mCrs;
      89        1026 :         poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
      90        1026 :         poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
      91        1026 :         poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
      92        1026 :         poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
      93        1026 :         poTMS->mWellKnownScaleSet =
      94        1026 :             "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible";
      95       32832 :         for (int i = 0; i <= 30; i++)
      96             :         {
      97       63612 :             TileMatrix tm;
      98       31806 :             tm.mId = CPLSPrintf("%d", i);
      99       31806 :             tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
     100       31806 :             tm.mResY = tm.mResX;
     101       31806 :             tm.mScaleDenominator = tm.mResX / 0.28e-3;
     102       31806 :             tm.mTopLeftX = -HALF_CIRCUMFERENCE;
     103       31806 :             tm.mTopLeftY = HALF_CIRCUMFERENCE;
     104       31806 :             tm.mTileWidth = 256;
     105       31806 :             tm.mTileHeight = 256;
     106       31806 :             tm.mMatrixWidth = 1 << i;
     107       31806 :             tm.mMatrixHeight = 1 << i;
     108       31806 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     109             :         }
     110        1026 :         return poTMS;
     111             :     }
     112             : 
     113        6642 :     if (EQUAL(fileOrDef, "WorldMercatorWGS84Quad") ||
     114        5864 :         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         778 :         poTMS->mTitle = "WorldMercatorWGS84Quad";
     119         778 :         poTMS->mIdentifier = "WorldMercatorWGS84Quad";
     120         778 :         poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3395";
     121         778 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     122         778 :         poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
     123         778 :         poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
     124         778 :         poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
     125         778 :         poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
     126         778 :         poTMS->mWellKnownScaleSet =
     127         778 :             "http://www.opengis.net/def/wkss/OGC/1.0/WorldMercatorWGS84Quad";
     128       24896 :         for (int i = 0; i <= 30; i++)
     129             :         {
     130       48236 :             TileMatrix tm;
     131       24118 :             tm.mId = CPLSPrintf("%d", i);
     132       24118 :             tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
     133       24118 :             tm.mResY = tm.mResX;
     134       24118 :             tm.mScaleDenominator = tm.mResX / 0.28e-3;
     135       24118 :             tm.mTopLeftX = -HALF_CIRCUMFERENCE;
     136       24118 :             tm.mTopLeftY = HALF_CIRCUMFERENCE;
     137       24118 :             tm.mTileWidth = 256;
     138       24118 :             tm.mTileHeight = 256;
     139       24118 :             tm.mMatrixWidth = 1 << i;
     140       24118 :             tm.mMatrixHeight = 1 << i;
     141       24118 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     142             :         }
     143         778 :         return poTMS;
     144             :     }
     145             : 
     146        5864 :     if (EQUAL(fileOrDef, "PseudoTMS_GlobalMercator"))
     147             :     {
     148             :         /* See global-mercator at
     149             :            http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
     150         778 :         poTMS->mTitle = "PseudoTMS_GlobalMercator";
     151         778 :         poTMS->mIdentifier = "PseudoTMS_GlobalMercator";
     152         778 :         poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
     153         778 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     154         778 :         poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
     155         778 :         poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
     156         778 :         poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
     157         778 :         poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
     158       24118 :         for (int i = 0; i <= 29; i++)
     159             :         {
     160       46680 :             TileMatrix tm;
     161       23340 :             tm.mId = CPLSPrintf("%d", i);
     162       23340 :             tm.mResX = HALF_CIRCUMFERENCE / 256 / (1 << i);
     163       23340 :             tm.mResY = tm.mResX;
     164       23340 :             tm.mScaleDenominator = tm.mResX / 0.28e-3;
     165       23340 :             tm.mTopLeftX = -HALF_CIRCUMFERENCE;
     166       23340 :             tm.mTopLeftY = HALF_CIRCUMFERENCE;
     167       23340 :             tm.mTileWidth = 256;
     168       23340 :             tm.mTileHeight = 256;
     169       23340 :             tm.mMatrixWidth = 2 * (1 << i);
     170       23340 :             tm.mMatrixHeight = 2 * (1 << i);
     171       23340 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     172             :         }
     173         778 :         return poTMS;
     174             :     }
     175             : 
     176        5086 :     if (EQUAL(fileOrDef, "InspireCRS84Quad") ||
     177        5080 :         EQUAL(fileOrDef, "PseudoTMS_GlobalGeodetic") ||
     178        5080 :         EQUAL(fileOrDef, "WorldCRS84Quad") ||
     179        4299 :         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         787 :         poTMS->mTitle = "WorldCRS84Quad";
     191         787 :         poTMS->mIdentifier = "WorldCRS84Quad";
     192         787 :         poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
     193         787 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     194         787 :         poTMS->mBbox.mLowerCornerX = -180;
     195         787 :         poTMS->mBbox.mLowerCornerY = -90;
     196         787 :         poTMS->mBbox.mUpperCornerX = 180;
     197         787 :         poTMS->mBbox.mUpperCornerY = 90;
     198         787 :         poTMS->mWellKnownScaleSet =
     199         787 :             "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       24397 :         for (int i = 0; i <= 29; i++)
     203             :         {
     204       47220 :             TileMatrix tm;
     205       23610 :             tm.mId = CPLSPrintf("%d", i);
     206       23610 :             tm.mResX = 180. / 256 / (1 << i);
     207       23610 :             tm.mResY = tm.mResX;
     208       23610 :             tm.mScaleDenominator =
     209       23610 :                 tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
     210       23610 :             tm.mTopLeftX = -180;
     211       23610 :             tm.mTopLeftY = 90;
     212       23610 :             tm.mTileWidth = 256;
     213       23610 :             tm.mTileHeight = 256;
     214       23610 :             tm.mMatrixWidth = 2 * (1 << i);
     215       23610 :             tm.mMatrixHeight = 1 << i;
     216       23610 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     217             :         }
     218         787 :         return poTMS;
     219             :     }
     220             : 
     221        4299 :     if (EQUAL(fileOrDef, "GoogleCRS84Quad") ||
     222        3503 :         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         796 :         poTMS->mTitle = "GoogleCRS84Quad";
     228         796 :         poTMS->mIdentifier = "GoogleCRS84Quad";
     229         796 :         poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
     230         796 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     231         796 :         poTMS->mBbox.mLowerCornerX = -180;
     232         796 :         poTMS->mBbox.mLowerCornerY = -90;
     233         796 :         poTMS->mBbox.mUpperCornerX = 180;
     234         796 :         poTMS->mBbox.mUpperCornerY = 90;
     235         796 :         poTMS->mWellKnownScaleSet =
     236         796 :             "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad";
     237       25472 :         for (int i = 0; i <= 30; i++)
     238             :         {
     239       49352 :             TileMatrix tm;
     240       24676 :             tm.mId = CPLSPrintf("%d", i);
     241       24676 :             tm.mResX = 360. / 256 / (1 << i);
     242       24676 :             tm.mResY = tm.mResX;
     243       24676 :             tm.mScaleDenominator =
     244       24676 :                 tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
     245       24676 :             tm.mTopLeftX = -180;
     246       24676 :             tm.mTopLeftY = 180;
     247       24676 :             tm.mTileWidth = 256;
     248       24676 :             tm.mTileHeight = 256;
     249       24676 :             tm.mMatrixWidth = 1 << i;
     250       24676 :             tm.mMatrixHeight = 1 << i;
     251       24676 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     252             :         }
     253         796 :         return poTMS;
     254             :     }
     255             : 
     256        3503 :     if (EQUAL(fileOrDef, "GlobalGeodeticOriginLat270"))
     257             :     {
     258             :         // gdal2tiles --profile=geodetic *without* --tmscompatible
     259         317 :         poTMS->mTitle = "GlobalGeodeticOriginLat270";
     260         317 :         poTMS->mIdentifier = "GlobalGeodeticOriginLat270";
     261         317 :         poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
     262         317 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     263         317 :         poTMS->mBbox.mLowerCornerX = -180;
     264         317 :         poTMS->mBbox.mLowerCornerY = -90;
     265         317 :         poTMS->mBbox.mUpperCornerX = 180;
     266         317 :         poTMS->mBbox.mUpperCornerY = 270;
     267       10144 :         for (int i = 0; i <= 30; i++)
     268             :         {
     269       19654 :             TileMatrix tm;
     270        9827 :             tm.mId = CPLSPrintf("%d", i);
     271        9827 :             tm.mResX = 360. / 256 / (1 << i);
     272        9827 :             tm.mResY = tm.mResX;
     273        9827 :             tm.mScaleDenominator =
     274        9827 :                 tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
     275        9827 :             tm.mTopLeftX = -180;
     276        9827 :             tm.mTopLeftY = 270;
     277        9827 :             tm.mTileWidth = 256;
     278        9827 :             tm.mTileHeight = 256;
     279        9827 :             tm.mMatrixWidth = 1 << i;
     280        9827 :             tm.mMatrixHeight = 1 << i;
     281        9827 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     282             :         }
     283         317 :         return poTMS;
     284             :     }
     285             : 
     286        3186 :     bool loadOk = false;
     287        3186 :     if (  // TMS 2.0 spec
     288        3186 :         (strstr(fileOrDef, "\"crs\"") &&
     289          47 :          strstr(fileOrDef, "\"tileMatrices\"")) ||
     290             :         // TMS 1.0 spec
     291        3164 :         (strstr(fileOrDef, "\"type\"") &&
     292          33 :          strstr(fileOrDef, "\"TileMatrixSetType\"")) ||
     293        3131 :         (strstr(fileOrDef, "\"identifier\"") &&
     294           1 :          strstr(fileOrDef, "\"boundingBox\"") &&
     295           1 :          strstr(fileOrDef, "\"tileMatrix\"")))
     296             :     {
     297          56 :         loadOk = oDoc.LoadMemory(fileOrDef);
     298             :     }
     299        3130 :     else if (STARTS_WITH_CI(fileOrDef, "http://") ||
     300        3129 :              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        3129 :         if (VSIStatL(fileOrDef, &sStat) == 0)
     309             :         {
     310           1 :             loadOk = oDoc.Load(fileOrDef);
     311             :         }
     312             :         else
     313             :         {
     314        3128 :             const char *pszFilename = CPLFindFile(
     315        6256 :                 "gdal", (std::string("tms_") + fileOrDef + ".json").c_str());
     316        3128 :             if (pszFilename)
     317             :             {
     318        3126 :                 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        3186 :     if (!loadOk)
     328             :     {
     329           4 :         return nullptr;
     330             :     }
     331             : 
     332        6364 :     auto oRoot = oDoc.GetRoot();
     333             :     const bool bIsTMSv2 =
     334        3182 :         oRoot.GetObj("crs").IsValid() && oRoot.GetObj("tileMatrices").IsValid();
     335             : 
     336        6365 :     if (!bIsTMSv2 && oRoot.GetString("type") != "TileMatrixSetType" &&
     337        3183 :         !oRoot.GetObj("tileMatrix").IsValid())
     338             :     {
     339           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     340             :                  "Expected type = TileMatrixSetType");
     341           0 :         return nullptr;
     342             :     }
     343             : 
     344        3992 :     const auto GetCRS = [](const CPLJSONObject &j)
     345             :     {
     346        3992 :         if (j.IsValid())
     347             :         {
     348        3991 :             if (j.GetType() == CPLJSONObject::Type::String)
     349        3988 :                 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        3182 :     poTMS->mIdentifier = oRoot.GetString(bIsTMSv2 ? "id" : "identifier");
     378        3182 :     poTMS->mTitle = oRoot.GetString("title");
     379        3182 :     poTMS->mAbstract = oRoot.GetString(bIsTMSv2 ? "description" : "abstract");
     380        9546 :     const auto oBbox = oRoot.GetObj("boundingBox");
     381        3182 :     if (oBbox.IsValid())
     382             :     {
     383         810 :         poTMS->mBbox.mCrs = GetCRS(oBbox.GetObj("crs"));
     384        2430 :         const auto oLowerCorner = oBbox.GetArray("lowerCorner");
     385         810 :         if (oLowerCorner.IsValid() && oLowerCorner.Size() == 2)
     386             :         {
     387         810 :             poTMS->mBbox.mLowerCornerX = oLowerCorner[0].ToDouble(NaN);
     388         810 :             poTMS->mBbox.mLowerCornerY = oLowerCorner[1].ToDouble(NaN);
     389             :         }
     390        2430 :         const auto oUpperCorner = oBbox.GetArray("upperCorner");
     391         810 :         if (oUpperCorner.IsValid() && oUpperCorner.Size() == 2)
     392             :         {
     393         810 :             poTMS->mBbox.mUpperCornerX = oUpperCorner[0].ToDouble(NaN);
     394         810 :             poTMS->mBbox.mUpperCornerY = oUpperCorner[1].ToDouble(NaN);
     395             :         }
     396             :     }
     397        3182 :     poTMS->mCrs = GetCRS(oRoot.GetObj(bIsTMSv2 ? "crs" : "supportedCRS"));
     398        3182 :     poTMS->mWellKnownScaleSet = oRoot.GetString("wellKnownScaleSet");
     399             : 
     400        6364 :     OGRSpatialReference oCrs;
     401        3182 :     if (oCrs.SetFromUserInput(
     402        3182 :             poTMS->mCrs.c_str(),
     403        3182 :             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        3181 :     double dfMetersPerUnit = 1.0;
     411        3181 :     if (oCrs.IsProjected())
     412             :     {
     413        3135 :         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        9543 :         oRoot.GetArray(bIsTMSv2 ? "tileMatrices" : "tileMatrix");
     422        3181 :     if (oTileMatrices.IsValid())
     423             :     {
     424        3181 :         double dfLastScaleDenominator = std::numeric_limits<double>::max();
     425       63923 :         for (const auto &oTM : oTileMatrices)
     426             :         {
     427       60748 :             TileMatrix tm;
     428       60748 :             tm.mId = oTM.GetString(bIsTMSv2 ? "id" : "identifier");
     429       60748 :             tm.mScaleDenominator = oTM.GetDouble("scaleDenominator");
     430       60748 :             if (tm.mScaleDenominator >= dfLastScaleDenominator ||
     431       60748 :                 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       60747 :             dfLastScaleDenominator = tm.mScaleDenominator;
     439             :             // See note g of Table 2 of
     440             :             // http://docs.opengeospatial.org/is/17-083r2/17-083r2.html
     441       60747 :             tm.mResX = tm.mScaleDenominator * 0.28e-3 / dfMetersPerUnit;
     442       60747 :             tm.mResY = tm.mResX;
     443       60747 :             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      121494 :                 oTM.GetArray(bIsTMSv2 ? "pointOfOrigin" : "topLeftCorner");
     455       60747 :             if (oTopLeftCorner.IsValid() && oTopLeftCorner.Size() == 2)
     456             :             {
     457       60747 :                 tm.mTopLeftX = oTopLeftCorner[0].ToDouble(NaN);
     458       60747 :                 tm.mTopLeftY = oTopLeftCorner[1].ToDouble(NaN);
     459             :             }
     460       60747 :             tm.mTileWidth = oTM.GetInteger("tileWidth");
     461       60747 :             if (tm.mTileWidth <= 0)
     462             :             {
     463           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileWidth: %d",
     464             :                          tm.mTileWidth);
     465           1 :                 return nullptr;
     466             :             }
     467       60746 :             tm.mTileHeight = oTM.GetInteger("tileHeight");
     468       60746 :             if (tm.mTileHeight <= 0)
     469             :             {
     470           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid tileHeight: %d",
     471             :                          tm.mTileHeight);
     472           1 :                 return nullptr;
     473             :             }
     474       60745 :             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       60744 :             tm.mMatrixWidth = oTM.GetInteger("matrixWidth");
     483       60744 :             if (tm.mMatrixWidth <= 0)
     484             :             {
     485           1 :                 CPLError(CE_Failure, CPLE_AppDefined, "Invalid matrixWidth: %d",
     486             :                          tm.mMatrixWidth);
     487           1 :                 return nullptr;
     488             :             }
     489       60743 :             tm.mMatrixHeight = oTM.GetInteger("matrixHeight");
     490       60743 :             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      182226 :                 bIsTMSv2 ? "variableMatrixWidths" : "variableMatrixWidth");
     499       60742 :             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       60742 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     512             :         }
     513             :     }
     514        3175 :     if (poTMS->mTileMatrixList.empty())
     515             :     {
     516           0 :         CPLError(CE_Failure, CPLE_AppDefined, "No tileMatrix defined");
     517           0 :         return nullptr;
     518             :     }
     519             : 
     520        3175 :     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        7119 : bool TileMatrixSet::hasVariableMatrixWidth() const
     581             : {
     582      193913 :     for (const auto &oTM : mTileMatrixList)
     583             :     {
     584      186796 :         if (!oTM.mVariableMatrixWidthList.empty())
     585             :         {
     586           2 :             return true;
     587             :         }
     588             :     }
     589        7117 :     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          58 : std::string TileMatrixSet::exportToTMSJsonV1() const
     640             : {
     641         116 :     CPLJSONObject oRoot;
     642          58 :     oRoot["type"] = "TileMatrixSetType";
     643          58 :     oRoot["title"] = mTitle;
     644          58 :     oRoot["identifier"] = mIdentifier;
     645          58 :     if (!mAbstract.empty())
     646           0 :         oRoot["abstract"] = mAbstract;
     647          58 :     if (!std::isnan(mBbox.mLowerCornerX))
     648             :     {
     649          58 :         CPLJSONObject oBbox;
     650          58 :         oBbox["type"] = "BoundingBoxType";
     651          58 :         oBbox["crs"] = mBbox.mCrs;
     652          58 :         oBbox["lowerCorner"] = {mBbox.mLowerCornerX, mBbox.mLowerCornerY};
     653          58 :         oBbox["upperCorner"] = {mBbox.mUpperCornerX, mBbox.mUpperCornerY};
     654          58 :         oRoot["boundingBox"] = std::move(oBbox);
     655             :     }
     656          58 :     oRoot["supportedCRS"] = mCrs;
     657          58 :     if (!mWellKnownScaleSet.empty())
     658          53 :         oRoot["wellKnownScaleSet"] = mWellKnownScaleSet;
     659             : 
     660          58 :     CPLJSONArray oTileMatrices;
     661         163 :     for (const auto &tm : mTileMatrixList)
     662             :     {
     663         210 :         CPLJSONObject oTM;
     664         105 :         oTM["type"] = "TileMatrixType";
     665         105 :         oTM["identifier"] = tm.mId;
     666         105 :         oTM["scaleDenominator"] = tm.mScaleDenominator;
     667         105 :         oTM["topLeftCorner"] = {tm.mTopLeftX, tm.mTopLeftY};
     668         105 :         oTM["tileWidth"] = tm.mTileWidth;
     669         105 :         oTM["tileHeight"] = tm.mTileHeight;
     670         105 :         oTM["matrixWidth"] = tm.mMatrixWidth;
     671         105 :         oTM["matrixHeight"] = tm.mMatrixHeight;
     672             : 
     673         105 :         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         105 :         oTileMatrices.Add(oTM);
     688             :     }
     689          58 :     oRoot["tileMatrix"] = oTileMatrices;
     690         116 :     return CPLString(oRoot.Format(CPLJSONObject::PrettyFormat::Pretty))
     691         116 :         .replaceAll("\\/", '/');
     692             : }
     693             : 
     694             : }  // namespace gdal
     695             : 
     696             : //! @endcond

Generated by: LCOV version 1.14