LCOV - code coverage report
Current view: top level - gcore - tilematrixset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 204 209 97.6 %
Date: 2025-01-18 12:42:00 Functions: 7 7 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 "ogr_spatialref.h"
      15             : 
      16             : #include <cmath>
      17             : #include <cfloat>
      18             : #include <limits>
      19             : 
      20             : #include "tilematrixset.hpp"
      21             : 
      22             : //! @cond Doxygen_Suppress
      23             : 
      24             : namespace gdal
      25             : {
      26             : 
      27             : /************************************************************************/
      28             : /*                   listPredefinedTileMatrixSets()                     */
      29             : /************************************************************************/
      30             : 
      31          61 : std::set<std::string> TileMatrixSet::listPredefinedTileMatrixSets()
      32             : {
      33         305 :     std::set<std::string> l{"GoogleMapsCompatible", "InspireCRS84Quad"};
      34          61 :     const char *pszSomeFile = CPLFindFile("gdal", "tms_NZTM2000.json");
      35          61 :     if (pszSomeFile)
      36             :     {
      37             :         CPLStringList aosList(
      38         122 :             VSIReadDir(CPLGetDirnameSafe(pszSomeFile).c_str()));
      39        9699 :         for (int i = 0; i < aosList.size(); i++)
      40             :         {
      41        9638 :             const size_t nLen = strlen(aosList[i]);
      42       19032 :             if (nLen > strlen("tms_") + strlen(".json") &&
      43        9882 :                 STARTS_WITH(aosList[i], "tms_") &&
      44         244 :                 EQUAL(aosList[i] + nLen - strlen(".json"), ".json"))
      45             :             {
      46         488 :                 std::string id(aosList[i] + strlen("tms_"),
      47         488 :                                nLen - (strlen("tms_") + strlen(".json")));
      48         244 :                 l.insert(id);
      49             :             }
      50             :         }
      51             :     }
      52          61 :     return l;
      53             : }
      54             : 
      55             : /************************************************************************/
      56             : /*                              parse()                                 */
      57             : /************************************************************************/
      58             : 
      59         505 : std::unique_ptr<TileMatrixSet> TileMatrixSet::parse(const char *fileOrDef)
      60             : {
      61        1010 :     CPLJSONDocument oDoc;
      62        1010 :     std::unique_ptr<TileMatrixSet> poTMS(new TileMatrixSet());
      63             : 
      64         505 :     constexpr double HALF_CIRCUMFERENCE = 6378137 * M_PI;
      65         505 :     if (EQUAL(fileOrDef, "GoogleMapsCompatible") ||
      66         370 :         EQUAL(
      67             :             fileOrDef,
      68             :             "http://www.opengis.net/def/tilematrixset/OGC/1.0/WebMercatorQuad"))
      69             :     {
      70             :         /* See http://portal.opengeospatial.org/files/?artifact_id=35326
      71             :          * (WMTS 1.0), Annex E.4 */
      72         135 :         poTMS->mTitle = "GoogleMapsCompatible";
      73         135 :         poTMS->mIdentifier = "GoogleMapsCompatible";
      74         135 :         poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
      75         135 :         poTMS->mBbox.mCrs = poTMS->mCrs;
      76         135 :         poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
      77         135 :         poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
      78         135 :         poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
      79         135 :         poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
      80         135 :         poTMS->mWellKnownScaleSet =
      81         135 :             "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible";
      82        4320 :         for (int i = 0; i <= 30; i++)
      83             :         {
      84        8370 :             TileMatrix tm;
      85        4185 :             tm.mId = CPLSPrintf("%d", i);
      86        4185 :             tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
      87        4185 :             tm.mResY = tm.mResX;
      88        4185 :             tm.mScaleDenominator = tm.mResX / 0.28e-3;
      89        4185 :             tm.mTopLeftX = -HALF_CIRCUMFERENCE;
      90        4185 :             tm.mTopLeftY = HALF_CIRCUMFERENCE;
      91        4185 :             tm.mTileWidth = 256;
      92        4185 :             tm.mTileHeight = 256;
      93        4185 :             tm.mMatrixWidth = 1 << i;
      94        4185 :             tm.mMatrixHeight = 1 << i;
      95        4185 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
      96             :         }
      97         135 :         return poTMS;
      98             :     }
      99             : 
     100         370 :     if (EQUAL(fileOrDef, "InspireCRS84Quad") ||
     101         304 :         EQUAL(
     102             :             fileOrDef,
     103             :             "http://www.opengis.net/def/tilematrixset/OGC/1.0/WorldCRS84Quad"))
     104             :     {
     105             :         /* See InspireCRS84Quad at
     106             :          * http://inspire.ec.europa.eu/documents/Network_Services/TechnicalGuidance_ViewServices_v3.0.pdf
     107             :          */
     108             :         /* This is exactly the same as PseudoTMS_GlobalGeodetic */
     109             :         /* See global-geodetic at
     110             :          * http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
     111             :         // See also http://docs.opengeospatial.org/is/17-083r2/17-083r2.html#76
     112          66 :         poTMS->mTitle = "InspireCRS84Quad";
     113          66 :         poTMS->mIdentifier = "InspireCRS84Quad";
     114          66 :         poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
     115          66 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     116          66 :         poTMS->mBbox.mLowerCornerX = -180;
     117          66 :         poTMS->mBbox.mLowerCornerY = -90;
     118          66 :         poTMS->mBbox.mUpperCornerX = 180;
     119          66 :         poTMS->mBbox.mUpperCornerY = 90;
     120          66 :         poTMS->mWellKnownScaleSet =
     121          66 :             "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad";
     122             :         // Limit to zoom level 29, because at that zoom level nMatrixWidth = 2 * (1 << 29) = 1073741824
     123             :         // and at 30 it would overflow int32.
     124        2046 :         for (int i = 0; i <= 29; i++)
     125             :         {
     126        3960 :             TileMatrix tm;
     127        1980 :             tm.mId = CPLSPrintf("%d", i);
     128        1980 :             tm.mResX = 180. / 256 / (1 << i);
     129        1980 :             tm.mResY = tm.mResX;
     130        1980 :             tm.mScaleDenominator =
     131        1980 :                 tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
     132        1980 :             tm.mTopLeftX = -180;
     133        1980 :             tm.mTopLeftY = 90;
     134        1980 :             tm.mTileWidth = 256;
     135        1980 :             tm.mTileHeight = 256;
     136        1980 :             tm.mMatrixWidth = 2 * (1 << i);
     137        1980 :             tm.mMatrixHeight = 1 << i;
     138        1980 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     139             :         }
     140          66 :         return poTMS;
     141             :     }
     142             : 
     143         304 :     bool loadOk = false;
     144         304 :     if (  // TMS 2.0 spec
     145         304 :         (strstr(fileOrDef, "\"crs\"") &&
     146          45 :          strstr(fileOrDef, "\"tileMatrices\"")) ||
     147             :         // TMS 1.0 spec
     148         282 :         (strstr(fileOrDef, "\"type\"") &&
     149          26 :          strstr(fileOrDef, "\"TileMatrixSetType\"")) ||
     150         256 :         (strstr(fileOrDef, "\"identifier\"") &&
     151           1 :          strstr(fileOrDef, "\"boundingBox\"") &&
     152           1 :          strstr(fileOrDef, "\"tileMatrix\"")))
     153             :     {
     154          49 :         loadOk = oDoc.LoadMemory(fileOrDef);
     155             :     }
     156         255 :     else if (STARTS_WITH_CI(fileOrDef, "http://") ||
     157         254 :              STARTS_WITH_CI(fileOrDef, "https://"))
     158             :     {
     159           1 :         const char *const apszOptions[] = {"MAX_FILE_SIZE=1000000", nullptr};
     160           1 :         loadOk = oDoc.LoadUrl(fileOrDef, apszOptions);
     161             :     }
     162             :     else
     163             :     {
     164             :         VSIStatBufL sStat;
     165         254 :         if (VSIStatL(fileOrDef, &sStat) == 0)
     166             :         {
     167           1 :             loadOk = oDoc.Load(fileOrDef);
     168             :         }
     169             :         else
     170             :         {
     171         253 :             const char *pszFilename = CPLFindFile(
     172         506 :                 "gdal", (std::string("tms_") + fileOrDef + ".json").c_str());
     173         253 :             if (pszFilename)
     174             :             {
     175         251 :                 loadOk = oDoc.Load(pszFilename);
     176             :             }
     177             :             else
     178             :             {
     179           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
     180             :                          "Invalid tiling matrix set name");
     181             :             }
     182             :         }
     183             :     }
     184         304 :     if (!loadOk)
     185             :     {
     186           4 :         return nullptr;
     187             :     }
     188             : 
     189         600 :     auto oRoot = oDoc.GetRoot();
     190             :     const bool bIsTMSv2 =
     191         300 :         oRoot.GetObj("crs").IsValid() && oRoot.GetObj("tileMatrices").IsValid();
     192             : 
     193         601 :     if (!bIsTMSv2 && oRoot.GetString("type") != "TileMatrixSetType" &&
     194         301 :         !oRoot.GetObj("tileMatrix").IsValid())
     195             :     {
     196           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     197             :                  "Expected type = TileMatrixSetType");
     198           0 :         return nullptr;
     199             :     }
     200             : 
     201         388 :     const auto GetCRS = [](const CPLJSONObject &j)
     202             :     {
     203         388 :         if (j.IsValid())
     204             :         {
     205         387 :             if (j.GetType() == CPLJSONObject::Type::String)
     206         384 :                 return j.ToString();
     207             : 
     208           3 :             else if (j.GetType() == CPLJSONObject::Type::Object)
     209             :             {
     210           6 :                 const std::string osURI = j.GetString("uri");
     211           3 :                 if (!osURI.empty())
     212           1 :                     return osURI;
     213             : 
     214             :                 // Quite a bit of confusion around wkt.
     215             :                 // See https://github.com/opengeospatial/ogcapi-tiles/issues/170
     216           4 :                 const auto jWKT = j.GetObj("wkt");
     217           2 :                 if (jWKT.GetType() == CPLJSONObject::Type::String)
     218             :                 {
     219           2 :                     const std::string osWKT = jWKT.ToString();
     220           1 :                     if (!osWKT.empty())
     221           1 :                         return osWKT;
     222             :                 }
     223           1 :                 else if (jWKT.GetType() == CPLJSONObject::Type::Object)
     224             :                 {
     225           2 :                     const std::string osWKT = jWKT.ToString();
     226           1 :                     if (!osWKT.empty())
     227           1 :                         return osWKT;
     228             :                 }
     229             :             }
     230             :         }
     231           1 :         return std::string();
     232             :     };
     233             : 
     234         300 :     poTMS->mIdentifier = oRoot.GetString(bIsTMSv2 ? "id" : "identifier");
     235         300 :     poTMS->mTitle = oRoot.GetString("title");
     236         300 :     poTMS->mAbstract = oRoot.GetString(bIsTMSv2 ? "description" : "abstract");
     237         900 :     const auto oBbox = oRoot.GetObj("boundingBox");
     238         300 :     if (oBbox.IsValid())
     239             :     {
     240          88 :         poTMS->mBbox.mCrs = GetCRS(oBbox.GetObj("crs"));
     241         264 :         const auto oLowerCorner = oBbox.GetArray("lowerCorner");
     242          88 :         if (oLowerCorner.IsValid() && oLowerCorner.Size() == 2)
     243             :         {
     244          88 :             poTMS->mBbox.mLowerCornerX = oLowerCorner[0].ToDouble(NaN);
     245          88 :             poTMS->mBbox.mLowerCornerY = oLowerCorner[1].ToDouble(NaN);
     246             :         }
     247         264 :         const auto oUpperCorner = oBbox.GetArray("upperCorner");
     248          88 :         if (oUpperCorner.IsValid() && oUpperCorner.Size() == 2)
     249             :         {
     250          88 :             poTMS->mBbox.mUpperCornerX = oUpperCorner[0].ToDouble(NaN);
     251          88 :             poTMS->mBbox.mUpperCornerY = oUpperCorner[1].ToDouble(NaN);
     252             :         }
     253             :     }
     254         300 :     poTMS->mCrs = GetCRS(oRoot.GetObj(bIsTMSv2 ? "crs" : "supportedCRS"));
     255         300 :     poTMS->mWellKnownScaleSet = oRoot.GetString("wellKnownScaleSet");
     256             : 
     257         600 :     OGRSpatialReference oCrs;
     258         300 :     if (oCrs.SetFromUserInput(
     259         300 :             poTMS->mCrs.c_str(),
     260         300 :             OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS) !=
     261             :         OGRERR_NONE)
     262             :     {
     263           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse CRS %s",
     264           1 :                  poTMS->mCrs.c_str());
     265           1 :         return nullptr;
     266             :     }
     267         299 :     double dfMetersPerUnit = 1.0;
     268         299 :     if (oCrs.IsProjected())
     269             :     {
     270         258 :         dfMetersPerUnit = oCrs.GetLinearUnits();
     271             :     }
     272          41 :     else if (oCrs.IsGeographic())
     273             :     {
     274          41 :         dfMetersPerUnit = oCrs.GetSemiMajor() * M_PI / 180;
     275             :     }
     276             : 
     277             :     const auto oTileMatrices =
     278         897 :         oRoot.GetArray(bIsTMSv2 ? "tileMatrices" : "tileMatrix");
     279         299 :     if (oTileMatrices.IsValid())
     280             :     {
     281         299 :         double dfLastScaleDenominator = std::numeric_limits<double>::max();
     282        5699 :         for (const auto &oTM : oTileMatrices)
     283             :         {
     284        5401 :             TileMatrix tm;
     285        5401 :             tm.mId = oTM.GetString(bIsTMSv2 ? "id" : "identifier");
     286        5401 :             tm.mScaleDenominator = oTM.GetDouble("scaleDenominator");
     287        5401 :             if (tm.mScaleDenominator >= dfLastScaleDenominator ||
     288        5401 :                 tm.mScaleDenominator <= 0)
     289             :             {
     290           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     291             :                          "Invalid scale denominator or non-decreasing series "
     292             :                          "of scale denominators");
     293           1 :                 return nullptr;
     294             :             }
     295        5400 :             dfLastScaleDenominator = tm.mScaleDenominator;
     296             :             // See note g of Table 2 of
     297             :             // http://docs.opengeospatial.org/is/17-083r2/17-083r2.html
     298        5400 :             tm.mResX = tm.mScaleDenominator * 0.28e-3 / dfMetersPerUnit;
     299        5400 :             tm.mResY = tm.mResX;
     300        5400 :             if (bIsTMSv2)
     301             :             {
     302        1635 :                 const auto osCornerOfOrigin = oTM.GetString("cornerOfOrigin");
     303         545 :                 if (!osCornerOfOrigin.empty() && osCornerOfOrigin != "topLeft")
     304             :                 {
     305           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     306             :                              "cornerOfOrigin = %s not supported",
     307             :                              osCornerOfOrigin.c_str());
     308             :                 }
     309             :             }
     310             :             const auto oTopLeftCorner =
     311       16200 :                 oTM.GetArray(bIsTMSv2 ? "pointOfOrigin" : "topLeftCorner");
     312        5400 :             if (oTopLeftCorner.IsValid() && oTopLeftCorner.Size() == 2)
     313             :             {
     314        5400 :                 tm.mTopLeftX = oTopLeftCorner[0].ToDouble(NaN);
     315        5400 :                 tm.mTopLeftY = oTopLeftCorner[1].ToDouble(NaN);
     316             :             }
     317        5400 :             tm.mTileWidth = oTM.GetInteger("tileWidth");
     318        5400 :             tm.mTileHeight = oTM.GetInteger("tileHeight");
     319        5400 :             tm.mMatrixWidth = oTM.GetInteger("matrixWidth");
     320        5400 :             tm.mMatrixHeight = oTM.GetInteger("matrixHeight");
     321             : 
     322             :             const auto oVariableMatrixWidths = oTM.GetArray(
     323       16200 :                 bIsTMSv2 ? "variableMatrixWidths" : "variableMatrixWidth");
     324        5400 :             if (oVariableMatrixWidths.IsValid())
     325             :             {
     326           8 :                 for (const auto &oVMW : oVariableMatrixWidths)
     327             :                 {
     328           5 :                     TileMatrix::VariableMatrixWidth vmw;
     329           5 :                     vmw.mCoalesce = oVMW.GetInteger("coalesce");
     330           5 :                     vmw.mMinTileRow = oVMW.GetInteger("minTileRow");
     331           5 :                     vmw.mMaxTileRow = oVMW.GetInteger("maxTileRow");
     332           5 :                     tm.mVariableMatrixWidthList.emplace_back(std::move(vmw));
     333             :                 }
     334             :             }
     335             : 
     336        5400 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     337             :         }
     338             :     }
     339         298 :     if (poTMS->mTileMatrixList.empty())
     340             :     {
     341           0 :         CPLError(CE_Failure, CPLE_AppDefined, "No tileMatrix defined");
     342           0 :         return nullptr;
     343             :     }
     344             : 
     345         298 :     return poTMS;
     346             : }
     347             : 
     348             : /************************************************************************/
     349             : /*                       haveAllLevelsSameTopLeft()                     */
     350             : /************************************************************************/
     351             : 
     352         456 : bool TileMatrixSet::haveAllLevelsSameTopLeft() const
     353             : {
     354       11413 :     for (const auto &oTM : mTileMatrixList)
     355             :     {
     356       21915 :         if (oTM.mTopLeftX != mTileMatrixList[0].mTopLeftX ||
     357       10957 :             oTM.mTopLeftY != mTileMatrixList[0].mTopLeftY)
     358             :         {
     359           1 :             return false;
     360             :         }
     361             :     }
     362         455 :     return true;
     363             : }
     364             : 
     365             : /************************************************************************/
     366             : /*                      haveAllLevelsSameTileSize()                     */
     367             : /************************************************************************/
     368             : 
     369         456 : bool TileMatrixSet::haveAllLevelsSameTileSize() const
     370             : {
     371       11413 :     for (const auto &oTM : mTileMatrixList)
     372             :     {
     373       21915 :         if (oTM.mTileWidth != mTileMatrixList[0].mTileWidth ||
     374       10957 :             oTM.mTileHeight != mTileMatrixList[0].mTileHeight)
     375             :         {
     376           1 :             return false;
     377             :         }
     378             :     }
     379         455 :     return true;
     380             : }
     381             : 
     382             : /************************************************************************/
     383             : /*                    hasOnlyPowerOfTwoVaryingScales()                  */
     384             : /************************************************************************/
     385             : 
     386         354 : bool TileMatrixSet::hasOnlyPowerOfTwoVaryingScales() const
     387             : {
     388        6749 :     for (size_t i = 1; i < mTileMatrixList.size(); i++)
     389             :     {
     390       12988 :         if (mTileMatrixList[i].mScaleDenominator == 0 ||
     391        6494 :             std::fabs(mTileMatrixList[i - 1].mScaleDenominator /
     392        6494 :                           mTileMatrixList[i].mScaleDenominator -
     393             :                       2) > 1e-10)
     394             :         {
     395          99 :             return false;
     396             :         }
     397             :     }
     398         255 :     return true;
     399             : }
     400             : 
     401             : /************************************************************************/
     402             : /*                        hasVariableMatrixWidth()                      */
     403             : /************************************************************************/
     404             : 
     405         359 : bool TileMatrixSet::hasVariableMatrixWidth() const
     406             : {
     407        9234 :     for (const auto &oTM : mTileMatrixList)
     408             :     {
     409        8877 :         if (!oTM.mVariableMatrixWidthList.empty())
     410             :         {
     411           2 :             return true;
     412             :         }
     413             :     }
     414         357 :     return false;
     415             : }
     416             : 
     417             : }  // namespace gdal
     418             : 
     419             : //! @endcond

Generated by: LCOV version 1.14