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