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

Generated by: LCOV version 1.14