LCOV - code coverage report
Current view: top level - gcore - tilematrixset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 186 191 97.4 %
Date: 2024-05-04 12:52:34 Functions: 6 6 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             :  * Permission is hereby granted, free of charge, to any person obtaining a
      11             :  * copy of this software and associated documentation files (the "Software"),
      12             :  * to deal in the Software without restriction, including without limitation
      13             :  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
      14             :  * and/or sell copies of the Software, and to permit persons to whom the
      15             :  * Software is furnished to do so, subject to the following conditions:
      16             :  *
      17             :  * The above copyright notice and this permission notice shall be included
      18             :  * in all copies or substantial portions of the Software.
      19             :  *
      20             :  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      21             :  * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      22             :  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      23             :  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
      24             :  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
      25             :  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
      26             :  * DEALINGS IN THE SOFTWARE.
      27             :  ****************************************************************************/
      28             : 
      29             : #include "cpl_json.h"
      30             : #include "ogr_spatialref.h"
      31             : 
      32             : #include <cmath>
      33             : #include <cfloat>
      34             : #include <limits>
      35             : 
      36             : #include "tilematrixset.hpp"
      37             : 
      38             : //! @cond Doxygen_Suppress
      39             : 
      40             : namespace gdal
      41             : {
      42             : 
      43             : /************************************************************************/
      44             : /*                   listPredefinedTileMatrixSets()                     */
      45             : /************************************************************************/
      46             : 
      47          47 : std::set<std::string> TileMatrixSet::listPredefinedTileMatrixSets()
      48             : {
      49         235 :     std::set<std::string> l{"GoogleMapsCompatible", "InspireCRS84Quad"};
      50          47 :     const char *pszSomeFile = CPLFindFile("gdal", "tms_NZTM2000.json");
      51          47 :     if (pszSomeFile)
      52             :     {
      53          94 :         CPLStringList aosList(VSIReadDir(CPLGetDirname(pszSomeFile)));
      54        7379 :         for (int i = 0; i < aosList.size(); i++)
      55             :         {
      56        7332 :             const size_t nLen = strlen(aosList[i]);
      57       14476 :             if (nLen > strlen("tms_") + strlen(".json") &&
      58        7520 :                 STARTS_WITH(aosList[i], "tms_") &&
      59         188 :                 EQUAL(aosList[i] + nLen - strlen(".json"), ".json"))
      60             :             {
      61         376 :                 std::string id(aosList[i] + strlen("tms_"),
      62         376 :                                nLen - (strlen("tms_") + strlen(".json")));
      63         188 :                 l.insert(id);
      64             :             }
      65             :         }
      66             :     }
      67          47 :     return l;
      68             : }
      69             : 
      70             : /************************************************************************/
      71             : /*                              parse()                                 */
      72             : /************************************************************************/
      73             : 
      74         415 : std::unique_ptr<TileMatrixSet> TileMatrixSet::parse(const char *fileOrDef)
      75             : {
      76         830 :     CPLJSONDocument oDoc;
      77         830 :     std::unique_ptr<TileMatrixSet> poTMS(new TileMatrixSet());
      78             : 
      79         415 :     constexpr double HALF_CIRCUMFERENCE = 6378137 * M_PI;
      80         415 :     if (EQUAL(fileOrDef, "GoogleMapsCompatible") ||
      81         295 :         EQUAL(
      82             :             fileOrDef,
      83             :             "http://www.opengis.net/def/tilematrixset/OGC/1.0/WebMercatorQuad"))
      84             :     {
      85             :         /* See http://portal.opengeospatial.org/files/?artifact_id=35326
      86             :          * (WMTS 1.0), Annex E.4 */
      87         120 :         poTMS->mTitle = "GoogleMapsCompatible";
      88         120 :         poTMS->mIdentifier = "GoogleMapsCompatible";
      89         120 :         poTMS->mCrs = "http://www.opengis.net/def/crs/EPSG/0/3857";
      90         120 :         poTMS->mBbox.mCrs = poTMS->mCrs;
      91         120 :         poTMS->mBbox.mLowerCornerX = -HALF_CIRCUMFERENCE;
      92         120 :         poTMS->mBbox.mLowerCornerY = -HALF_CIRCUMFERENCE;
      93         120 :         poTMS->mBbox.mUpperCornerX = HALF_CIRCUMFERENCE;
      94         120 :         poTMS->mBbox.mUpperCornerY = HALF_CIRCUMFERENCE;
      95         120 :         poTMS->mWellKnownScaleSet =
      96         120 :             "http://www.opengis.net/def/wkss/OGC/1.0/GoogleMapsCompatible";
      97        3840 :         for (int i = 0; i <= 30; i++)
      98             :         {
      99        7440 :             TileMatrix tm;
     100        3720 :             tm.mId = CPLSPrintf("%d", i);
     101        3720 :             tm.mResX = 2 * HALF_CIRCUMFERENCE / 256 / (1 << i);
     102        3720 :             tm.mResY = tm.mResX;
     103        3720 :             tm.mScaleDenominator = tm.mResX / 0.28e-3;
     104        3720 :             tm.mTopLeftX = -HALF_CIRCUMFERENCE;
     105        3720 :             tm.mTopLeftY = HALF_CIRCUMFERENCE;
     106        3720 :             tm.mTileWidth = 256;
     107        3720 :             tm.mTileHeight = 256;
     108        3720 :             tm.mMatrixWidth = 1 << i;
     109        3720 :             tm.mMatrixHeight = 1 << i;
     110        3720 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     111             :         }
     112         120 :         return poTMS;
     113             :     }
     114             : 
     115         295 :     if (EQUAL(fileOrDef, "InspireCRS84Quad") ||
     116         243 :         EQUAL(
     117             :             fileOrDef,
     118             :             "http://www.opengis.net/def/tilematrixset/OGC/1.0/WorldCRS84Quad"))
     119             :     {
     120             :         /* See InspireCRS84Quad at
     121             :          * http://inspire.ec.europa.eu/documents/Network_Services/TechnicalGuidance_ViewServices_v3.0.pdf
     122             :          */
     123             :         /* This is exactly the same as PseudoTMS_GlobalGeodetic */
     124             :         /* See global-geodetic at
     125             :          * http://wiki.osgeo.org/wiki/Tile_Map_Service_Specification */
     126             :         // See also http://docs.opengeospatial.org/is/17-083r2/17-083r2.html#76
     127          52 :         poTMS->mTitle = "InspireCRS84Quad";
     128          52 :         poTMS->mIdentifier = "InspireCRS84Quad";
     129          52 :         poTMS->mCrs = "http://www.opengis.net/def/crs/OGC/1.3/CRS84";
     130          52 :         poTMS->mBbox.mCrs = poTMS->mCrs;
     131          52 :         poTMS->mBbox.mLowerCornerX = -180;
     132          52 :         poTMS->mBbox.mLowerCornerY = -90;
     133          52 :         poTMS->mBbox.mUpperCornerX = 180;
     134          52 :         poTMS->mBbox.mUpperCornerY = 90;
     135          52 :         poTMS->mWellKnownScaleSet =
     136          52 :             "http://www.opengis.net/def/wkss/OGC/1.0/GoogleCRS84Quad";
     137             :         // Limit to zoom level 29, because at that zoom level nMatrixWidth = 2 * (1 << 29) = 1073741824
     138             :         // and at 30 it would overflow int32.
     139        1612 :         for (int i = 0; i <= 29; i++)
     140             :         {
     141        3120 :             TileMatrix tm;
     142        1560 :             tm.mId = CPLSPrintf("%d", i);
     143        1560 :             tm.mResX = 180. / 256 / (1 << i);
     144        1560 :             tm.mResY = tm.mResX;
     145        1560 :             tm.mScaleDenominator =
     146        1560 :                 tm.mResX * (HALF_CIRCUMFERENCE / 180) / 0.28e-3;
     147        1560 :             tm.mTopLeftX = -180;
     148        1560 :             tm.mTopLeftY = 90;
     149        1560 :             tm.mTileWidth = 256;
     150        1560 :             tm.mTileHeight = 256;
     151        1560 :             tm.mMatrixWidth = 2 * (1 << i);
     152        1560 :             tm.mMatrixHeight = 1 << i;
     153        1560 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     154             :         }
     155          52 :         return poTMS;
     156             :     }
     157             : 
     158         243 :     bool loadOk = false;
     159         243 :     if (  // TMS 2.0 spec
     160         243 :         (strstr(fileOrDef, "\"crs\"") &&
     161          40 :          strstr(fileOrDef, "\"tileMatrices\"")) ||
     162             :         // TMS 1.0 spec
     163         224 :         (strstr(fileOrDef, "\"type\"") &&
     164          24 :          strstr(fileOrDef, "\"TileMatrixSetType\"")) ||
     165         200 :         (strstr(fileOrDef, "\"identifier\"") &&
     166           1 :          strstr(fileOrDef, "\"boundingBox\"") &&
     167           1 :          strstr(fileOrDef, "\"tileMatrix\"")))
     168             :     {
     169          44 :         loadOk = oDoc.LoadMemory(fileOrDef);
     170             :     }
     171         199 :     else if (STARTS_WITH_CI(fileOrDef, "http://") ||
     172         198 :              STARTS_WITH_CI(fileOrDef, "https://"))
     173             :     {
     174           1 :         const char *const apszOptions[] = {"MAX_FILE_SIZE=1000000", nullptr};
     175           1 :         loadOk = oDoc.LoadUrl(fileOrDef, apszOptions);
     176             :     }
     177             :     else
     178             :     {
     179             :         VSIStatBufL sStat;
     180         198 :         if (VSIStatL(fileOrDef, &sStat) == 0)
     181             :         {
     182           1 :             loadOk = oDoc.Load(fileOrDef);
     183             :         }
     184             :         else
     185             :         {
     186         197 :             const char *pszFilename = CPLFindFile(
     187         394 :                 "gdal", (std::string("tms_") + fileOrDef + ".json").c_str());
     188         197 :             if (pszFilename)
     189             :             {
     190         195 :                 loadOk = oDoc.Load(pszFilename);
     191             :             }
     192             :             else
     193             :             {
     194           2 :                 CPLError(CE_Failure, CPLE_AppDefined,
     195             :                          "Invalid tiling matrix set name");
     196             :             }
     197             :         }
     198             :     }
     199         243 :     if (!loadOk)
     200             :     {
     201           4 :         return nullptr;
     202             :     }
     203             : 
     204         478 :     auto oRoot = oDoc.GetRoot();
     205             :     const bool bIsTMSv2 =
     206         239 :         oRoot.GetObj("crs").IsValid() && oRoot.GetObj("tileMatrices").IsValid();
     207             : 
     208         479 :     if (!bIsTMSv2 && oRoot.GetString("type") != "TileMatrixSetType" &&
     209         240 :         !oRoot.GetObj("tileMatrix").IsValid())
     210             :     {
     211           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     212             :                  "Expected type = TileMatrixSetType");
     213           0 :         return nullptr;
     214             :     }
     215             : 
     216         239 :     poTMS->mIdentifier = oRoot.GetString(bIsTMSv2 ? "id" : "identifier");
     217         239 :     poTMS->mTitle = oRoot.GetString("title");
     218         239 :     poTMS->mAbstract = oRoot.GetString(bIsTMSv2 ? "description" : "abstract");
     219         717 :     const auto oBbox = oRoot.GetObj("boundingBox");
     220         239 :     if (oBbox.IsValid())
     221             :     {
     222          72 :         poTMS->mBbox.mCrs = oBbox.GetString("crs");
     223         216 :         const auto oLowerCorner = oBbox.GetArray("lowerCorner");
     224          72 :         if (oLowerCorner.IsValid() && oLowerCorner.Size() == 2)
     225             :         {
     226          72 :             poTMS->mBbox.mLowerCornerX = oLowerCorner[0].ToDouble(NaN);
     227          72 :             poTMS->mBbox.mLowerCornerY = oLowerCorner[1].ToDouble(NaN);
     228             :         }
     229         216 :         const auto oUpperCorner = oBbox.GetArray("upperCorner");
     230          72 :         if (oUpperCorner.IsValid() && oUpperCorner.Size() == 2)
     231             :         {
     232          72 :             poTMS->mBbox.mUpperCornerX = oUpperCorner[0].ToDouble(NaN);
     233          72 :             poTMS->mBbox.mUpperCornerY = oUpperCorner[1].ToDouble(NaN);
     234             :         }
     235             :     }
     236         239 :     poTMS->mCrs = oRoot.GetString(bIsTMSv2 ? "crs" : "supportedCRS");
     237         239 :     poTMS->mWellKnownScaleSet = oRoot.GetString("wellKnownScaleSet");
     238             : 
     239         478 :     OGRSpatialReference oCrs;
     240         239 :     if (oCrs.SetFromUserInput(
     241         239 :             poTMS->mCrs.c_str(),
     242         239 :             OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS) !=
     243             :         OGRERR_NONE)
     244             :     {
     245           1 :         CPLError(CE_Failure, CPLE_AppDefined, "Cannot parse CRS %s",
     246           1 :                  poTMS->mCrs.c_str());
     247           1 :         return nullptr;
     248             :     }
     249         238 :     double dfMetersPerUnit = 1.0;
     250         238 :     if (oCrs.IsProjected())
     251             :     {
     252         202 :         dfMetersPerUnit = oCrs.GetLinearUnits();
     253             :     }
     254          36 :     else if (oCrs.IsGeographic())
     255             :     {
     256          36 :         dfMetersPerUnit = oCrs.GetSemiMajor() * M_PI / 180;
     257             :     }
     258             : 
     259             :     const auto oTileMatrices =
     260         714 :         oRoot.GetArray(bIsTMSv2 ? "tileMatrices" : "tileMatrix");
     261         238 :     if (oTileMatrices.IsValid())
     262             :     {
     263         238 :         double dfLastScaleDenominator = std::numeric_limits<double>::max();
     264        4551 :         for (const auto &oTM : oTileMatrices)
     265             :         {
     266        4314 :             TileMatrix tm;
     267        4314 :             tm.mId = oTM.GetString(bIsTMSv2 ? "id" : "identifier");
     268        4314 :             tm.mScaleDenominator = oTM.GetDouble("scaleDenominator");
     269        4314 :             if (tm.mScaleDenominator >= dfLastScaleDenominator ||
     270        4314 :                 tm.mScaleDenominator <= 0)
     271             :             {
     272           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     273             :                          "Invalid scale denominator or non-decreasing series "
     274             :                          "of scale denominators");
     275           1 :                 return nullptr;
     276             :             }
     277        4313 :             dfLastScaleDenominator = tm.mScaleDenominator;
     278             :             // See note g of Table 2 of
     279             :             // http://docs.opengeospatial.org/is/17-083r2/17-083r2.html
     280        4313 :             tm.mResX = tm.mScaleDenominator * 0.28e-3 / dfMetersPerUnit;
     281        4313 :             tm.mResY = tm.mResX;
     282        4313 :             if (bIsTMSv2)
     283             :             {
     284        1626 :                 const auto osCornerOfOrigin = oTM.GetString("cornerOfOrigin");
     285         542 :                 if (!osCornerOfOrigin.empty() && osCornerOfOrigin != "topLeft")
     286             :                 {
     287           0 :                     CPLError(CE_Warning, CPLE_AppDefined,
     288             :                              "cornerOfOrigin = %s not supported",
     289             :                              osCornerOfOrigin.c_str());
     290             :                 }
     291             :             }
     292             :             const auto oTopLeftCorner =
     293       12939 :                 oTM.GetArray(bIsTMSv2 ? "pointOfOrigin" : "topLeftCorner");
     294        4313 :             if (oTopLeftCorner.IsValid() && oTopLeftCorner.Size() == 2)
     295             :             {
     296        4313 :                 tm.mTopLeftX = oTopLeftCorner[0].ToDouble(NaN);
     297        4313 :                 tm.mTopLeftY = oTopLeftCorner[1].ToDouble(NaN);
     298             :             }
     299        4313 :             tm.mTileWidth = oTM.GetInteger("tileWidth");
     300        4313 :             tm.mTileHeight = oTM.GetInteger("tileHeight");
     301        4313 :             tm.mMatrixWidth = oTM.GetInteger("matrixWidth");
     302        4313 :             tm.mMatrixHeight = oTM.GetInteger("matrixHeight");
     303             : 
     304             :             const auto oVariableMatrixWidths = oTM.GetArray(
     305       12939 :                 bIsTMSv2 ? "variableMatrixWidths" : "variableMatrixWidth");
     306        4313 :             if (oVariableMatrixWidths.IsValid())
     307             :             {
     308           8 :                 for (const auto &oVMW : oVariableMatrixWidths)
     309             :                 {
     310           5 :                     TileMatrix::VariableMatrixWidth vmw;
     311           5 :                     vmw.mCoalesce = oVMW.GetInteger("coalesce");
     312           5 :                     vmw.mMinTileRow = oVMW.GetInteger("minTileRow");
     313           5 :                     vmw.mMaxTileRow = oVMW.GetInteger("maxTileRow");
     314           5 :                     tm.mVariableMatrixWidthList.emplace_back(std::move(vmw));
     315             :                 }
     316             :             }
     317             : 
     318        4313 :             poTMS->mTileMatrixList.emplace_back(std::move(tm));
     319             :         }
     320             :     }
     321         237 :     if (poTMS->mTileMatrixList.empty())
     322             :     {
     323           0 :         CPLError(CE_Failure, CPLE_AppDefined, "No tileMatrix defined");
     324           0 :         return nullptr;
     325             :     }
     326             : 
     327         237 :     return poTMS;
     328             : }
     329             : 
     330             : /************************************************************************/
     331             : /*                       haveAllLevelsSameTopLeft()                     */
     332             : /************************************************************************/
     333             : 
     334         371 : bool TileMatrixSet::haveAllLevelsSameTopLeft() const
     335             : {
     336        9365 :     for (const auto &oTM : mTileMatrixList)
     337             :     {
     338       17989 :         if (oTM.mTopLeftX != mTileMatrixList[0].mTopLeftX ||
     339        8994 :             oTM.mTopLeftY != mTileMatrixList[0].mTopLeftY)
     340             :         {
     341           1 :             return false;
     342             :         }
     343             :     }
     344         370 :     return true;
     345             : }
     346             : 
     347             : /************************************************************************/
     348             : /*                      haveAllLevelsSameTileSize()                     */
     349             : /************************************************************************/
     350             : 
     351         371 : bool TileMatrixSet::haveAllLevelsSameTileSize() const
     352             : {
     353        9365 :     for (const auto &oTM : mTileMatrixList)
     354             :     {
     355       17989 :         if (oTM.mTileWidth != mTileMatrixList[0].mTileWidth ||
     356        8994 :             oTM.mTileHeight != mTileMatrixList[0].mTileHeight)
     357             :         {
     358           1 :             return false;
     359             :         }
     360             :     }
     361         370 :     return true;
     362             : }
     363             : 
     364             : /************************************************************************/
     365             : /*                    hasOnlyPowerOfTwoVaryingScales()                  */
     366             : /************************************************************************/
     367             : 
     368         300 : bool TileMatrixSet::hasOnlyPowerOfTwoVaryingScales() const
     369             : {
     370        5813 :     for (size_t i = 1; i < mTileMatrixList.size(); i++)
     371             :     {
     372       11188 :         if (mTileMatrixList[i].mScaleDenominator == 0 ||
     373        5594 :             std::fabs(mTileMatrixList[i - 1].mScaleDenominator /
     374        5594 :                           mTileMatrixList[i].mScaleDenominator -
     375             :                       2) > 1e-10)
     376             :         {
     377          81 :             return false;
     378             :         }
     379             :     }
     380         219 :     return true;
     381             : }
     382             : 
     383             : /************************************************************************/
     384             : /*                        hasVariableMatrixWidth()                      */
     385             : /************************************************************************/
     386             : 
     387         292 : bool TileMatrixSet::hasVariableMatrixWidth() const
     388             : {
     389        7591 :     for (const auto &oTM : mTileMatrixList)
     390             :     {
     391        7301 :         if (!oTM.mVariableMatrixWidthList.empty())
     392             :         {
     393           2 :             return true;
     394             :         }
     395             :     }
     396         290 :     return false;
     397             : }
     398             : 
     399             : }  // namespace gdal
     400             : 
     401             : //! @endcond

Generated by: LCOV version 1.14