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