LCOV - code coverage report
Current view: top level - frmts/stacit - stacitdataset.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 350 429 81.6 %
Date: 2024-05-13 13:33:37 Functions: 12 12 100.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  GDAL
       4             :  * Purpose:  STACIT (Spatio-Temporal Asset Catalog ITems) driver
       5             :  * Author:   Even Rouault, <even dot rouault at spatialys.com>
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2021, 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 "vrtdataset.h"
      31             : #include "ogr_spatialref.h"
      32             : 
      33             : #include <algorithm>
      34             : #include <limits>
      35             : #include <map>
      36             : #include <string>
      37             : 
      38             : namespace
      39             : {
      40             : 
      41             : struct AssetItem
      42             : {
      43             :     std::string osFilename{};
      44             :     std::string osDateTime{};
      45             :     int nXSize = 0;
      46             :     int nYSize = 0;
      47             :     double dfXMin = 0;
      48             :     double dfYMin = 0;
      49             :     double dfXMax = 0;
      50             :     double dfYMax = 0;
      51             : };
      52             : 
      53             : struct AssetSetByProjection
      54             : {
      55             :     std::string osProjUserString{};
      56             :     std::vector<AssetItem> assets{};
      57             : };
      58             : 
      59             : struct Asset
      60             : {
      61             :     std::string osName{};
      62             :     CPLJSONArray eoBands{};
      63             :     std::map<std::string, AssetSetByProjection> assets{};
      64             : };
      65             : 
      66             : struct Collection
      67             : {
      68             :     std::string osName{};
      69             :     std::map<std::string, Asset> assets{};
      70             : };
      71             : }  // namespace
      72             : 
      73             : /************************************************************************/
      74             : /*                            STACITDataset                             */
      75             : /************************************************************************/
      76             : 
      77             : class STACITDataset final : public VRTDataset
      78             : {
      79             :     bool Open(GDALOpenInfo *poOpenInfo);
      80             :     bool SetupDataset(GDALOpenInfo *poOpenInfo,
      81             :                       const std::string &osSTACITFilename,
      82             :                       std::map<std::string, Collection> &oMapCollection);
      83             :     void
      84             :     SetSubdatasets(const std::string &osFilename,
      85             :                    const std::map<std::string, Collection> &oMapCollection);
      86             : 
      87             :   public:
      88             :     STACITDataset();
      89             : 
      90             :     static int Identify(GDALOpenInfo *poOpenInfo);
      91             :     static GDALDataset *OpenStatic(GDALOpenInfo *poOpenInfo);
      92             : };
      93             : 
      94             : /************************************************************************/
      95             : /*                          STACITDataset()                             */
      96             : /************************************************************************/
      97             : 
      98          10 : STACITDataset::STACITDataset() : VRTDataset(0, 0)
      99             : {
     100          10 :     SetWritable(false);
     101          10 : }
     102             : 
     103             : /************************************************************************/
     104             : /*                             Identify()                               */
     105             : /************************************************************************/
     106             : 
     107       47824 : int STACITDataset::Identify(GDALOpenInfo *poOpenInfo)
     108             : {
     109       47824 :     if (STARTS_WITH(poOpenInfo->pszFilename, "STACIT:"))
     110             :     {
     111          12 :         return true;
     112             :     }
     113             : 
     114       47812 :     if (poOpenInfo->nHeaderBytes == 0)
     115             :     {
     116       45145 :         return false;
     117             :     }
     118             : 
     119        7967 :     for (int i = 0; i < 2; i++)
     120             :     {
     121             :         // TryToIngest() may reallocate pabyHeader, so do not move this
     122             :         // before the loop.
     123        5314 :         const char *pszHeader =
     124             :             reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
     125        5314 :         if (strstr(pszHeader, "\"stac_version\"") != nullptr &&
     126           8 :             strstr(pszHeader, "\"proj:transform\"") != nullptr)
     127             :         {
     128           8 :             return true;
     129             :         }
     130             : 
     131        5306 :         if (i == 0)
     132             :         {
     133             :             // Should be enough for a STACIT .json file
     134        2653 :             poOpenInfo->TryToIngest(32768);
     135             :         }
     136             :     }
     137             : 
     138        2653 :     return false;
     139             : }
     140             : 
     141             : /************************************************************************/
     142             : /*                        SanitizeCRSValue()                            */
     143             : /************************************************************************/
     144             : 
     145          16 : static std::string SanitizeCRSValue(const std::string &v)
     146             : {
     147          16 :     std::string ret;
     148          16 :     bool lastWasAlphaNum = true;
     149          76 :     for (char ch : v)
     150             :     {
     151          60 :         if (!isalnum(static_cast<unsigned char>(ch)))
     152             :         {
     153           6 :             if (lastWasAlphaNum)
     154           6 :                 ret += '_';
     155           6 :             lastWasAlphaNum = false;
     156             :         }
     157             :         else
     158             :         {
     159          54 :             ret += ch;
     160          54 :             lastWasAlphaNum = true;
     161             :         }
     162             :     }
     163          16 :     if (!ret.empty() && ret.back() == '_')
     164           0 :         ret.resize(ret.size() - 1);
     165          16 :     return ret;
     166             : }
     167             : 
     168             : /************************************************************************/
     169             : /*                            ParseAsset()                              */
     170             : /************************************************************************/
     171             : 
     172          18 : static void ParseAsset(const CPLJSONObject &jAsset,
     173             :                        const CPLJSONObject &oProperties,
     174             :                        const std::string &osCollection,
     175             :                        const std::string &osFilteredCRS,
     176             :                        std::map<std::string, Collection> &oMapCollection)
     177             : {
     178             :     // Skip assets that are obviously not images
     179          36 :     const auto osType = jAsset["type"].ToString();
     180          34 :     if (osType == "application/json" || osType == "application/xml" ||
     181          16 :         osType == "text/plain")
     182             :     {
     183           2 :         return;
     184             :     }
     185             : 
     186             :     // Skip assets whose role is obviously non georeferenced
     187          32 :     const auto oRoles = jAsset.GetArray("roles");
     188          16 :     if (oRoles.IsValid())
     189             :     {
     190          32 :         for (const auto &oRole : oRoles)
     191             :         {
     192          32 :             const auto osRole = oRole.ToString();
     193          32 :             if (osRole == "thumbnail" || osRole == "info" ||
     194          16 :                 osRole == "metadata")
     195             :             {
     196           0 :                 return;
     197             :             }
     198             :         }
     199             :     }
     200             : 
     201          16 :     const auto osAssetName = jAsset.GetName();
     202             : 
     203          32 :     const auto osHref = jAsset["href"].ToString();
     204          16 :     if (osHref.empty())
     205             :     {
     206           0 :         CPLError(CE_Warning, CPLE_AppDefined, "Missing href on asset %s",
     207             :                  osAssetName.c_str());
     208           0 :         return;
     209             :     }
     210             : 
     211             :     const auto GetAssetOrFeatureProperty =
     212          87 :         [&oProperties, &jAsset](const char *pszName)
     213             :     {
     214         174 :         auto obj = jAsset[pszName];
     215          58 :         if (obj.IsValid())
     216          29 :             return obj;
     217          29 :         return oProperties[pszName];
     218          16 :     };
     219             : 
     220          16 :     auto oProjEPSG = GetAssetOrFeatureProperty("proj:epsg");
     221          16 :     std::string osProjUserString;
     222          16 :     if (oProjEPSG.IsValid() && oProjEPSG.GetType() != CPLJSONObject::Type::Null)
     223             :     {
     224          16 :         osProjUserString = "EPSG:" + oProjEPSG.ToString();
     225             :     }
     226             :     else
     227             :     {
     228           0 :         auto oProjWKT2 = GetAssetOrFeatureProperty("proj:wkt2");
     229           0 :         if (oProjWKT2.IsValid() &&
     230           0 :             oProjWKT2.GetType() == CPLJSONObject::Type::String)
     231             :         {
     232           0 :             osProjUserString = oProjWKT2.ToString();
     233             :         }
     234             :         else
     235             :         {
     236           0 :             auto oProjPROJJSON = GetAssetOrFeatureProperty("proj:projjson");
     237           0 :             if (oProjPROJJSON.IsValid() &&
     238           0 :                 oProjPROJJSON.GetType() == CPLJSONObject::Type::Object)
     239             :             {
     240           0 :                 osProjUserString = oProjPROJJSON.ToString();
     241             :             }
     242             :             else
     243             :             {
     244           0 :                 CPLDebug("STACIT",
     245             :                          "Skipping asset %s that lacks a valid CRS member",
     246             :                          osAssetName.c_str());
     247           0 :                 return;
     248             :             }
     249             :         }
     250             :     }
     251             : 
     252          20 :     if (!osFilteredCRS.empty() &&
     253          20 :         osFilteredCRS != SanitizeCRSValue(osProjUserString))
     254             :     {
     255           2 :         return;
     256             :     }
     257             : 
     258          14 :     AssetItem item;
     259          14 :     item.osFilename = osHref;
     260          14 :     item.osDateTime = oProperties["datetime"].ToString();
     261             : 
     262             :     // Figure out item bounds and width/height
     263          14 :     auto oProjBBOX = GetAssetOrFeatureProperty("proj:bbox").ToArray();
     264          14 :     auto oProjShape = GetAssetOrFeatureProperty("proj:shape").ToArray();
     265          14 :     auto oProjTransform = GetAssetOrFeatureProperty("proj:transform").ToArray();
     266          14 :     const bool bIsBBOXValid = oProjBBOX.IsValid() && oProjBBOX.Size() == 4;
     267          14 :     const bool bIsShapeValid = oProjShape.IsValid() && oProjShape.Size() == 2;
     268             :     const bool bIsTransformValid =
     269          28 :         oProjTransform.IsValid() &&
     270          14 :         (oProjTransform.Size() == 6 || oProjTransform.Size() == 9);
     271          14 :     std::vector<double> bbox;
     272          14 :     if (bIsBBOXValid)
     273             :     {
     274          25 :         for (const auto &oItem : oProjBBOX)
     275          20 :             bbox.push_back(oItem.ToDouble());
     276           5 :         CPLAssert(bbox.size() == 4);
     277             :     }
     278          14 :     std::vector<int> shape;
     279          14 :     if (bIsShapeValid)
     280             :     {
     281          27 :         for (const auto &oItem : oProjShape)
     282          18 :             shape.push_back(oItem.ToInteger());
     283           9 :         CPLAssert(shape.size() == 2);
     284             :     }
     285          14 :     std::vector<double> transform;
     286          14 :     if (bIsTransformValid)
     287             :     {
     288         134 :         for (const auto &oItem : oProjTransform)
     289         120 :             transform.push_back(oItem.ToDouble());
     290          14 :         CPLAssert(transform.size() == 6 || transform.size() == 9);
     291             : #if defined(__GNUC__)
     292             : #pragma GCC diagnostic push
     293             : #pragma GCC diagnostic ignored "-Wnull-dereference"
     294             : #endif
     295          28 :         if (transform[0] <= 0 || transform[1] != 0 || transform[3] != 0 ||
     296          54 :             transform[4] >= 0 ||
     297          14 :             (transform.size() == 9 &&
     298          12 :              (transform[6] != 0 || transform[7] != 0 || transform[8] != 1)))
     299             :         {
     300           0 :             CPLError(
     301             :                 CE_Warning, CPLE_AppDefined,
     302             :                 "Skipping asset %s because its proj:transform is "
     303             :                 "not of the form [xres,0,xoffset,0,yres<0,yoffset[,0,0,1]]",
     304             :                 osAssetName.c_str());
     305           0 :             return;
     306             :         }
     307             : #if defined(__GNUC__)
     308             : #pragma GCC diagnostic pop
     309             : #endif
     310             :     }
     311             : 
     312          14 :     if (bIsBBOXValid && bIsShapeValid)
     313             :     {
     314           0 :         item.nXSize = shape[1];
     315           0 :         item.nYSize = shape[0];
     316           0 :         item.dfXMin = bbox[0];
     317           0 :         item.dfYMin = bbox[1];
     318           0 :         item.dfXMax = bbox[2];
     319           0 :         item.dfYMax = bbox[3];
     320             :     }
     321          14 :     else if (bIsBBOXValid && bIsTransformValid)
     322             :     {
     323           5 :         item.dfXMin = bbox[0];
     324           5 :         item.dfYMin = bbox[1];
     325           5 :         item.dfXMax = bbox[2];
     326           5 :         item.dfYMax = bbox[3];
     327           5 :         if (item.dfXMin != transform[2] || item.dfYMax != transform[5])
     328             :         {
     329           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     330             :                      "Skipping asset %s because the origin of "
     331             :                      "proj:transform and proj:bbox are not consistent",
     332             :                      osAssetName.c_str());
     333           0 :             return;
     334             :         }
     335           5 :         double dfXSize = (item.dfXMax - item.dfXMin) / transform[0];
     336           5 :         double dfYSize = (item.dfYMax - item.dfYMin) / -transform[4];
     337           5 :         if (!(dfXSize < INT_MAX && dfYSize < INT_MAX))
     338           0 :             return;
     339           5 :         item.nXSize = static_cast<int>(dfXSize);
     340           5 :         item.nYSize = static_cast<int>(dfYSize);
     341             :     }
     342           9 :     else if (bIsShapeValid && bIsTransformValid)
     343             :     {
     344           9 :         item.nXSize = shape[1];
     345           9 :         item.nYSize = shape[0];
     346           9 :         item.dfXMin = transform[2];
     347           9 :         item.dfYMax = transform[5];
     348           9 :         item.dfXMax = item.dfXMin + item.nXSize * transform[0];
     349           9 :         item.dfYMin = item.dfYMax + item.nYSize * transform[4];
     350             :     }
     351             :     else
     352             :     {
     353           0 :         CPLDebug("STACIT",
     354             :                  "Skipping asset %s that lacks at least 2 members among "
     355             :                  "proj:bbox, proj:shape and proj:transform",
     356             :                  osAssetName.c_str());
     357           0 :         return;
     358             :     }
     359             : 
     360          14 :     if (item.nXSize <= 0 || item.nYSize <= 0)
     361             :     {
     362           0 :         CPLError(CE_Warning, CPLE_AppDefined,
     363             :                  "Skipping asset %s because the size is invalid",
     364             :                  osAssetName.c_str());
     365           0 :         return;
     366             :     }
     367             : 
     368             :     // Create/fetch collection
     369          14 :     if (oMapCollection.find(osCollection) == oMapCollection.end())
     370             :     {
     371          18 :         Collection collection;
     372           9 :         collection.osName = osCollection;
     373           9 :         oMapCollection[osCollection] = std::move(collection);
     374             :     }
     375          14 :     auto &collection = oMapCollection[osCollection];
     376             : 
     377             :     // Create/fetch asset in collection
     378          14 :     if (collection.assets.find(osAssetName) == collection.assets.end())
     379             :     {
     380          20 :         Asset asset;
     381          10 :         asset.osName = osAssetName;
     382          10 :         asset.eoBands = jAsset.GetArray("eo:bands");
     383             : 
     384          10 :         collection.assets[osAssetName] = std::move(asset);
     385             :     }
     386          14 :     auto &asset = collection.assets[osAssetName];
     387             : 
     388             :     // Create/fetch projection in asset
     389          14 :     if (asset.assets.find(osProjUserString) == asset.assets.end())
     390             :     {
     391          22 :         AssetSetByProjection assetByProj;
     392          11 :         assetByProj.osProjUserString = osProjUserString;
     393          11 :         asset.assets[osProjUserString] = std::move(assetByProj);
     394             :     }
     395          14 :     auto &assets = asset.assets[osProjUserString];
     396             : 
     397             :     // Add item
     398          14 :     assets.assets.emplace_back(item);
     399             : }
     400             : 
     401             : /************************************************************************/
     402             : /*                           SetupDataset()                             */
     403             : /************************************************************************/
     404             : 
     405           7 : bool STACITDataset::SetupDataset(
     406             :     GDALOpenInfo *poOpenInfo, const std::string &osSTACITFilename,
     407             :     std::map<std::string, Collection> &oMapCollection)
     408             : {
     409           7 :     auto &collection = oMapCollection.begin()->second;
     410           7 :     auto &asset = collection.assets.begin()->second;
     411           7 :     auto &assetByProj = asset.assets.begin()->second;
     412           7 :     auto &items = assetByProj.assets;
     413             : 
     414             :     // Compute global bounds and resolution
     415           7 :     double dfXMin = std::numeric_limits<double>::max();
     416           7 :     double dfYMin = std::numeric_limits<double>::max();
     417           7 :     double dfXMax = -std::numeric_limits<double>::max();
     418           7 :     double dfYMax = -std::numeric_limits<double>::max();
     419           7 :     double dfXRes = 0;
     420           7 :     double dfYRes = 0;
     421          14 :     const char *pszResolution = CSLFetchNameValueDef(
     422           7 :         poOpenInfo->papszOpenOptions, "RESOLUTION", "AVERAGE");
     423          17 :     for (const auto &assetItem : items)
     424             :     {
     425          10 :         dfXMin = std::min(dfXMin, assetItem.dfXMin);
     426          10 :         dfYMin = std::min(dfYMin, assetItem.dfYMin);
     427          10 :         dfXMax = std::max(dfXMax, assetItem.dfXMax);
     428          10 :         dfYMax = std::max(dfYMax, assetItem.dfYMax);
     429          10 :         const double dfThisXRes =
     430          10 :             (assetItem.dfXMax - assetItem.dfXMin) / assetItem.nXSize;
     431          10 :         const double dfThisYRes =
     432          10 :             (assetItem.dfYMax - assetItem.dfYMin) / assetItem.nYSize;
     433             : #ifdef DEBUG_VERBOSE
     434             :         CPLDebug("STACIT", "%s -> resx=%f resy=%f",
     435             :                  assetItem.osFilename.c_str(), dfThisXRes, dfThisYRes);
     436             : #endif
     437          10 :         if (dfXRes != 0 && EQUAL(pszResolution, "HIGHEST"))
     438             :         {
     439           0 :             dfXRes = std::min(dfXRes, dfThisXRes);
     440           0 :             dfYRes = std::min(dfYRes, dfThisYRes);
     441             :         }
     442          10 :         else if (dfXRes != 0 && EQUAL(pszResolution, "LOWEST"))
     443             :         {
     444           0 :             dfXRes = std::max(dfXRes, dfThisXRes);
     445           0 :             dfYRes = std::max(dfYRes, dfThisYRes);
     446             :         }
     447             :         else
     448             :         {
     449          10 :             dfXRes += dfThisXRes;
     450          10 :             dfYRes += dfThisYRes;
     451             :         }
     452             :     }
     453           7 :     if (EQUAL(pszResolution, "AVERAGE"))
     454             :     {
     455           7 :         dfXRes /= static_cast<int>(items.size());
     456           7 :         dfYRes /= static_cast<int>(items.size());
     457             :     }
     458             : 
     459             :     // Set raster size
     460           7 :     double dfXSize = std::round((dfXMax - dfXMin) / dfXRes);
     461           7 :     double dfYSize = std::round((dfYMax - dfYMin) / dfYRes);
     462           7 :     if (dfXSize <= 0 || dfYSize <= 0 || dfXSize > INT_MAX || dfYSize > INT_MAX)
     463             :     {
     464           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     465             :                  "Invalid computed dataset dimensions");
     466           0 :         return false;
     467             :     }
     468           7 :     nRasterXSize = static_cast<int>(dfXSize);
     469           7 :     nRasterYSize = static_cast<int>(dfYSize);
     470             : 
     471             :     // Set geotransform
     472             :     double adfGeoTransform[6];
     473           7 :     adfGeoTransform[0] = dfXMin;
     474           7 :     adfGeoTransform[1] = dfXRes;
     475           7 :     adfGeoTransform[2] = 0;
     476           7 :     adfGeoTransform[3] = dfYMax;
     477           7 :     adfGeoTransform[4] = 0;
     478           7 :     adfGeoTransform[5] = -dfYRes;
     479           7 :     SetGeoTransform(adfGeoTransform);
     480             : 
     481             :     // Set SRS
     482          14 :     OGRSpatialReference oSRS;
     483           7 :     if (oSRS.SetFromUserInput(
     484             :             assetByProj.osProjUserString.c_str(),
     485           7 :             OGRSpatialReference::SET_FROM_USER_INPUT_LIMITATIONS_get()) ==
     486             :         OGRERR_NONE)
     487             :     {
     488           7 :         oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
     489           7 :         SetSpatialRef(&oSRS);
     490             :     }
     491             : 
     492             :     // Open of the items to find the number of bands, their data type
     493             :     // and nodata.
     494             :     const auto BuildVSICurlFilename =
     495          17 :         [&osSTACITFilename, &collection](const std::string &osFilename)
     496             :     {
     497          17 :         std::string osRet;
     498          17 :         if (STARTS_WITH(osFilename.c_str(), "http"))
     499             :         {
     500           0 :             if (STARTS_WITH(osSTACITFilename.c_str(),
     501             :                             "https://planetarycomputer.microsoft.com/api/"))
     502             :             {
     503           0 :                 osRet = "/vsicurl?pc_url_signing=yes&pc_collection=";
     504           0 :                 osRet += collection.osName;
     505           0 :                 osRet += "&url=";
     506             :                 char *pszEncoded =
     507           0 :                     CPLEscapeString(osFilename.c_str(), -1, CPLES_URL);
     508           0 :                 CPLString osEncoded(pszEncoded);
     509           0 :                 CPLFree(pszEncoded);
     510             :                 // something is confused if the whole URL appears as
     511             :                 // /vsicurl...blabla_without_slash.tif" so transform back %2F to
     512             :                 // /
     513           0 :                 osEncoded.replaceAll("%2F", '/');
     514           0 :                 osRet += osEncoded;
     515             :             }
     516             :             else
     517             :             {
     518           0 :                 osRet = "/vsicurl/";
     519           0 :                 osRet += osFilename;
     520             :             }
     521             :         }
     522          17 :         else if (STARTS_WITH(osFilename.c_str(), "file://"))
     523             :         {
     524           1 :             osRet = osFilename.substr(strlen("file://"));
     525             :         }
     526          16 :         else if (STARTS_WITH(osFilename.c_str(), "s3://"))
     527             :         {
     528           0 :             osRet = "/vsis3/";
     529           0 :             osRet += osFilename.substr(strlen("s3://"));
     530             :         }
     531             : 
     532             :         else
     533             :         {
     534          16 :             osRet = osFilename;
     535             :         }
     536          17 :         return osRet;
     537           7 :     };
     538             : 
     539          14 :     const auto osFirstItemName(BuildVSICurlFilename(items.front().osFilename));
     540             :     auto poItemDS = std::unique_ptr<GDALDataset>(
     541          14 :         GDALDataset::Open(osFirstItemName.c_str()));
     542           7 :     if (!poItemDS)
     543             :     {
     544           0 :         CPLError(CE_Failure, CPLE_AppDefined,
     545             :                  "Cannot open %s to retrieve band characteristics",
     546             :                  osFirstItemName.c_str());
     547           0 :         return false;
     548             :     }
     549             : 
     550             :     // Sort by ascending datetime
     551           7 :     std::sort(items.begin(), items.end(),
     552           6 :               [](const AssetItem &a, const AssetItem &b)
     553             :               {
     554           6 :                   if (!a.osDateTime.empty() && !b.osDateTime.empty())
     555           6 :                       return a.osDateTime < b.osDateTime;
     556           0 :                   return &a < &b;
     557             :               });
     558             : 
     559             :     // Create VRT bands and add sources
     560          14 :     for (int i = 0; i < poItemDS->GetRasterCount(); i++)
     561             :     {
     562           7 :         auto poItemBand = poItemDS->GetRasterBand(i + 1);
     563           7 :         AddBand(poItemBand->GetRasterDataType(), nullptr);
     564             :         auto poVRTBand =
     565           7 :             cpl::down_cast<VRTSourcedRasterBand *>(GetRasterBand(i + 1));
     566           7 :         int bHasNoData = FALSE;
     567           7 :         const double dfNoData = poItemBand->GetNoDataValue(&bHasNoData);
     568           7 :         if (bHasNoData)
     569           0 :             poVRTBand->SetNoDataValue(dfNoData);
     570             : 
     571           7 :         const auto eInterp = poItemBand->GetColorInterpretation();
     572           7 :         if (eInterp != GCI_Undefined)
     573           7 :             poVRTBand->SetColorInterpretation(eInterp);
     574             : 
     575             :         // Set band properties
     576          14 :         if (asset.eoBands.IsValid() &&
     577           7 :             asset.eoBands.Size() == poItemDS->GetRasterCount())
     578             :         {
     579          14 :             const auto &eoBand = asset.eoBands[i];
     580          21 :             const auto osBandName = eoBand["name"].ToString();
     581           7 :             if (!osBandName.empty())
     582           7 :                 poVRTBand->SetDescription(osBandName.c_str());
     583             : 
     584           7 :             if (eInterp != GCI_Undefined)
     585             :             {
     586          21 :                 const auto osCommonName = eoBand["common_name"].ToString();
     587           7 :                 if (osCommonName == "red")
     588           0 :                     poVRTBand->SetColorInterpretation(GCI_RedBand);
     589           7 :                 else if (osCommonName == "green")
     590           0 :                     poVRTBand->SetColorInterpretation(GCI_GreenBand);
     591           7 :                 else if (osCommonName == "blue")
     592           0 :                     poVRTBand->SetColorInterpretation(GCI_BlueBand);
     593           7 :                 else if (osCommonName == "alpha")
     594           0 :                     poVRTBand->SetColorInterpretation(GCI_AlphaBand);
     595             :             }
     596             : 
     597          23 :             for (const auto &eoBandChild : eoBand.GetChildren())
     598             :             {
     599          32 :                 const auto osChildName = eoBandChild.GetName();
     600          16 :                 if (osChildName != "name" && osChildName != "common_name")
     601             :                 {
     602           6 :                     poVRTBand->SetMetadataItem(osChildName.c_str(),
     603          12 :                                                eoBandChild.ToString().c_str());
     604             :                 }
     605             :             }
     606             :         }
     607             : 
     608             :         // Add items as VRT sources
     609          17 :         for (const auto &assetItem : items)
     610             :         {
     611          20 :             const auto osItemName(BuildVSICurlFilename(assetItem.osFilename));
     612          10 :             const double dfDstXOff = (assetItem.dfXMin - dfXMin) / dfXRes;
     613          10 :             const double dfDstXSize =
     614          10 :                 (assetItem.dfXMax - assetItem.dfXMin) / dfXRes;
     615          10 :             const double dfDstYOff = (dfYMax - assetItem.dfYMax) / dfYRes;
     616          10 :             const double dfDstYSize =
     617          10 :                 (assetItem.dfYMax - assetItem.dfYMin) / dfYRes;
     618          10 :             if (!bHasNoData)
     619             :             {
     620          10 :                 poVRTBand->AddSimpleSource(osItemName.c_str(), i + 1, 0, 0,
     621          10 :                                            assetItem.nXSize, assetItem.nYSize,
     622             :                                            dfDstXOff, dfDstYOff, dfDstXSize,
     623             :                                            dfDstYSize);
     624             :             }
     625             :             else
     626             :             {
     627           0 :                 poVRTBand->AddComplexSource(osItemName.c_str(), i + 1, 0, 0,
     628           0 :                                             assetItem.nXSize, assetItem.nYSize,
     629             :                                             dfDstXOff, dfDstYOff, dfDstXSize,
     630             :                                             dfDstYSize,
     631             :                                             0.0,  // offset
     632             :                                             1.0,  // scale
     633             :                                             dfNoData);
     634             :             }
     635             :         }
     636             : 
     637           7 :         const char *apszOptions[] = {"EMIT_ERROR_IF_GEOS_NOT_AVAILABLE=NO",
     638             :                                      nullptr};
     639           7 :         poVRTBand->RemoveCoveredSources(apszOptions);
     640             :     }
     641           7 :     return true;
     642             : }
     643             : 
     644             : /************************************************************************/
     645             : /*                         SetSubdatasets()                             */
     646             : /************************************************************************/
     647             : 
     648           1 : void STACITDataset::SetSubdatasets(
     649             :     const std::string &osFilename,
     650             :     const std::map<std::string, Collection> &oMapCollection)
     651             : {
     652           2 :     CPLStringList aosSubdatasets;
     653           1 :     int nCount = 1;
     654           3 :     for (const auto &collectionKV : oMapCollection)
     655             :     {
     656           5 :         for (const auto &assetKV : collectionKV.second.assets)
     657             :         {
     658           6 :             std::string osCollectionAssetArg;
     659           3 :             if (oMapCollection.size() > 1)
     660             :                 osCollectionAssetArg +=
     661           3 :                     "collection=" + collectionKV.first + ",";
     662           3 :             osCollectionAssetArg += "asset=" + assetKV.first;
     663             : 
     664           6 :             std::string osCollectionAssetText;
     665           3 :             if (oMapCollection.size() > 1)
     666             :                 osCollectionAssetText +=
     667           3 :                     "Collection " + collectionKV.first + ", ";
     668           3 :             osCollectionAssetText += "Asset " + assetKV.first;
     669             : 
     670           3 :             if (assetKV.second.assets.size() == 1)
     671             :             {
     672             :                 aosSubdatasets.AddString(CPLSPrintf(
     673             :                     "SUBDATASET_%d_NAME=STACIT:\"%s\":%s", nCount,
     674           2 :                     osFilename.c_str(), osCollectionAssetArg.c_str()));
     675             :                 aosSubdatasets.AddString(CPLSPrintf(
     676             :                     "SUBDATASET_%d_DESC=%s of %s", nCount,
     677           2 :                     osCollectionAssetText.c_str(), osFilename.c_str()));
     678           2 :                 nCount++;
     679             :             }
     680             :             else
     681             :             {
     682           3 :                 for (const auto &assetsByProjKV : assetKV.second.assets)
     683             :                 {
     684             :                     aosSubdatasets.AddString(CPLSPrintf(
     685             :                         "SUBDATASET_%d_NAME=STACIT:\"%s\":%s,crs=%s", nCount,
     686             :                         osFilename.c_str(), osCollectionAssetArg.c_str(),
     687           2 :                         SanitizeCRSValue(assetsByProjKV.first).c_str()));
     688             :                     aosSubdatasets.AddString(CPLSPrintf(
     689             :                         "SUBDATASET_%d_DESC=%s of %s in CRS %s", nCount,
     690             :                         osCollectionAssetText.c_str(), osFilename.c_str(),
     691           2 :                         assetsByProjKV.first.c_str()));
     692           2 :                     nCount++;
     693             :                 }
     694             :             }
     695             :         }
     696             :     }
     697           1 :     GDALDataset::SetMetadata(aosSubdatasets.List(), "SUBDATASETS");
     698           1 : }
     699             : 
     700             : /************************************************************************/
     701             : /*                               Open()                                 */
     702             : /************************************************************************/
     703             : 
     704          10 : bool STACITDataset::Open(GDALOpenInfo *poOpenInfo)
     705             : {
     706          20 :     CPLString osFilename(poOpenInfo->pszFilename);
     707             :     std::string osFilteredCollection =
     708          20 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "COLLECTION", "");
     709             :     std::string osFilteredAsset =
     710          20 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "ASSET", "");
     711             :     std::string osFilteredCRS = SanitizeCRSValue(
     712          30 :         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "CRS", ""));
     713          10 :     if (STARTS_WITH(poOpenInfo->pszFilename, "STACIT:"))
     714             :     {
     715             :         const CPLStringList aosTokens(CSLTokenizeString2(
     716           6 :             poOpenInfo->pszFilename, ":", CSLT_HONOURSTRINGS));
     717           6 :         if (aosTokens.size() != 2 && aosTokens.size() != 3)
     718           0 :             return false;
     719           6 :         osFilename = aosTokens[1];
     720           6 :         if (aosTokens.size() >= 3)
     721             :         {
     722             :             const CPLStringList aosFilters(
     723          12 :                 CSLTokenizeString2(aosTokens[2], ",", 0));
     724             :             osFilteredCollection = aosFilters.FetchNameValueDef(
     725           6 :                 "collection", osFilteredCollection.c_str());
     726             :             osFilteredAsset =
     727           6 :                 aosFilters.FetchNameValueDef("asset", osFilteredAsset.c_str());
     728             :             osFilteredCRS =
     729           6 :                 aosFilters.FetchNameValueDef("crs", osFilteredCRS.c_str());
     730             :         }
     731             :     }
     732             : 
     733          20 :     std::map<std::string, Collection> oMapCollection;
     734          10 :     GIntBig nItemIter = 0;
     735          10 :     GIntBig nMaxItems = CPLAtoGIntBig(CSLFetchNameValueDef(
     736          10 :         poOpenInfo->papszOpenOptions, "MAX_ITEMS", "1000"));
     737             : 
     738          10 :     if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "MAX_ITEMS") == nullptr)
     739             :     {
     740             :         // If the URL includes a limit parameter, and it's larger than our
     741             :         // default MAX_ITEMS value, then increase the later to the former.
     742           9 :         const auto nPos = osFilename.ifind("&limit=");
     743           9 :         if (nPos != std::string::npos)
     744             :         {
     745           0 :             const auto nLimit = CPLAtoGIntBig(
     746           0 :                 osFilename.substr(nPos + strlen("&limit=")).c_str());
     747           0 :             nMaxItems = std::max(nMaxItems, nLimit);
     748             :         }
     749             :     }
     750             : 
     751          20 :     auto osCurFilename = osFilename;
     752           1 :     do
     753             :     {
     754          11 :         CPLJSONDocument oDoc;
     755          22 :         if (STARTS_WITH(osCurFilename, "http://") ||
     756          11 :             STARTS_WITH(osCurFilename, "https://"))
     757             :         {
     758           0 :             if (!oDoc.LoadUrl(osCurFilename, nullptr))
     759           0 :                 return false;
     760             :         }
     761             :         else
     762             :         {
     763          11 :             if (!oDoc.Load(osCurFilename))
     764           0 :                 return false;
     765             :         }
     766          11 :         const auto oRoot = oDoc.GetRoot();
     767          22 :         const auto oFeatures = oRoot.GetArray("features");
     768          11 :         if (!oFeatures.IsValid())
     769             :         {
     770           0 :             CPLError(CE_Failure, CPLE_AppDefined, "Missing features");
     771           0 :             return false;
     772             :         }
     773          38 :         for (const auto &oFeature : oFeatures)
     774             :         {
     775          27 :             nItemIter++;
     776          27 :             if (nItemIter > nMaxItems)
     777             :             {
     778           0 :                 break;
     779             :             }
     780             : 
     781          54 :             auto oStacExtensions = oFeature.GetArray("stac_extensions");
     782          27 :             if (!oStacExtensions.IsValid())
     783             :             {
     784           0 :                 CPLDebug("STACIT",
     785             :                          "Skipping Feature that lacks stac_extensions");
     786           0 :                 continue;
     787             :             }
     788          27 :             bool bProjExtensionFound = false;
     789          81 :             for (const auto &oStacExtension : oStacExtensions)
     790             :             {
     791         135 :                 if (oStacExtension.ToString() == "proj" ||
     792          81 :                     oStacExtension.ToString().find(
     793             :                         "https://stac-extensions.github.io/projection/") == 0)
     794             :                 {
     795          27 :                     bProjExtensionFound = true;
     796          27 :                     break;
     797             :                 }
     798             :             }
     799          27 :             if (!bProjExtensionFound)
     800             :             {
     801           0 :                 CPLDebug(
     802             :                     "STACIT",
     803             :                     "Skipping Feature that lacks the 'proj' STAC extension");
     804           0 :                 continue;
     805             :             }
     806             : 
     807          54 :             auto jAssets = oFeature["assets"];
     808          54 :             if (!jAssets.IsValid() ||
     809          27 :                 jAssets.GetType() != CPLJSONObject::Type::Object)
     810             :             {
     811           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     812             :                          "Missing assets on a Feature");
     813           0 :                 continue;
     814             :             }
     815             : 
     816          54 :             auto oProperties = oFeature["properties"];
     817          54 :             if (!oProperties.IsValid() ||
     818          27 :                 oProperties.GetType() != CPLJSONObject::Type::Object)
     819             :             {
     820           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     821             :                          "Missing properties on a Feature");
     822           0 :                 continue;
     823             :             }
     824             : 
     825          54 :             const auto osCollection = oFeature["collection"].ToString();
     826          42 :             if (!osFilteredCollection.empty() &&
     827          15 :                 osFilteredCollection != osCollection)
     828           8 :                 continue;
     829             : 
     830          45 :             for (const auto &jAsset : jAssets.GetChildren())
     831             :             {
     832          26 :                 const auto osAssetName = jAsset.GetName();
     833          26 :                 if (!osFilteredAsset.empty() && osFilteredAsset != osAssetName)
     834           8 :                     continue;
     835             : 
     836          18 :                 ParseAsset(jAsset, oProperties, osCollection, osFilteredCRS,
     837             :                            oMapCollection);
     838             :             }
     839             :         }
     840          11 :         if (nItemIter >= nMaxItems)
     841             :         {
     842           1 :             if (CSLFetchNameValue(poOpenInfo->papszOpenOptions, "MAX_ITEMS") ==
     843             :                 nullptr)
     844             :             {
     845           0 :                 CPLError(CE_Warning, CPLE_AppDefined,
     846             :                          "Maximum number of items (" CPL_FRMT_GIB
     847             :                          ") allowed to be retrieved has been hit",
     848             :                          nMaxItems);
     849             :             }
     850             :             else
     851             :             {
     852           1 :                 CPLDebug("STACIT",
     853             :                          "Maximum number of items (" CPL_FRMT_GIB
     854             :                          ") allowed to be retrieved has been hit",
     855             :                          nMaxItems);
     856             :             }
     857           1 :             break;
     858             :         }
     859             : 
     860             :         // Follow next link
     861          20 :         const auto oLinks = oRoot.GetArray("links");
     862          10 :         if (!oLinks.IsValid())
     863           9 :             break;
     864           2 :         std::string osNewFilename;
     865           2 :         for (const auto &oLink : oLinks)
     866             :         {
     867           1 :             if (oLink["rel"].ToString() == "next")
     868             :             {
     869           1 :                 osNewFilename = oLink["href"].ToString();
     870           1 :                 break;
     871             :             }
     872             :         }
     873           1 :         if (!osNewFilename.empty() && osNewFilename != osCurFilename)
     874           1 :             osCurFilename = osNewFilename;
     875             :         else
     876           0 :             osCurFilename.clear();
     877           1 :     } while (!osCurFilename.empty());
     878             : 
     879          10 :     if (oMapCollection.empty())
     880             :     {
     881           2 :         CPLError(CE_Failure, CPLE_AppDefined, "No compatible asset found");
     882           2 :         return false;
     883             :     }
     884             : 
     885          15 :     if (oMapCollection.size() > 1 ||
     886          15 :         oMapCollection.begin()->second.assets.size() > 1 ||
     887          15 :         oMapCollection.begin()->second.assets.begin()->second.assets.size() > 1)
     888             :     {
     889             :         // If there's more than one asset type or more than one SRS, expose
     890             :         // subdatasets.
     891           1 :         SetSubdatasets(osFilename, oMapCollection);
     892           1 :         return true;
     893             :     }
     894             :     else
     895             :     {
     896           7 :         return SetupDataset(poOpenInfo, osFilename, oMapCollection);
     897             :     }
     898             : }
     899             : 
     900             : /************************************************************************/
     901             : /*                            OpenStatic()                              */
     902             : /************************************************************************/
     903             : 
     904          10 : GDALDataset *STACITDataset::OpenStatic(GDALOpenInfo *poOpenInfo)
     905             : {
     906          10 :     if (!Identify(poOpenInfo))
     907           0 :         return nullptr;
     908          20 :     auto poDS = std::make_unique<STACITDataset>();
     909          10 :     if (!poDS->Open(poOpenInfo))
     910           2 :         return nullptr;
     911           8 :     return poDS.release();
     912             : }
     913             : 
     914             : /************************************************************************/
     915             : /*                       GDALRegister_STACIT()                          */
     916             : /************************************************************************/
     917             : 
     918        1522 : void GDALRegister_STACIT()
     919             : 
     920             : {
     921        1522 :     if (GDALGetDriverByName("STACIT") != nullptr)
     922         301 :         return;
     923             : 
     924        1221 :     GDALDriver *poDriver = new GDALDriver();
     925             : 
     926        1221 :     poDriver->SetDescription("STACIT");
     927        1221 :     poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
     928        1221 :     poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
     929        1221 :                               "Spatio-Temporal Asset Catalog Items");
     930        1221 :     poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/stacit.html");
     931        1221 :     poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
     932        1221 :     poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
     933        1221 :     poDriver->SetMetadataItem(
     934             :         GDAL_DMD_OPENOPTIONLIST,
     935             :         "<OpenOptionList>"
     936             :         "   <Option name='MAX_ITEMS' type='int' default='1000' "
     937             :         "description='Maximum number of items fetched. 0=unlimited'/>"
     938             :         "   <Option name='COLLECTION' type='string' "
     939             :         "description='Name of collection to filter items'/>"
     940             :         "   <Option name='ASSET' type='string' "
     941             :         "description='Name of asset to filter items'/>"
     942             :         "   <Option name='CRS' type='string' "
     943             :         "description='Name of CRS to filter items'/>"
     944             :         "   <Option name='RESOLUTION' type='string-select' default='AVERAGE' "
     945             :         "description='Strategy to use to determine dataset resolution'>"
     946             :         "       <Value>AVERAGE</Value>"
     947             :         "       <Value>HIGHEST</Value>"
     948             :         "       <Value>LOWEST</Value>"
     949             :         "   </Option>"
     950        1221 :         "</OpenOptionList>");
     951             : 
     952        1221 :     poDriver->pfnOpen = STACITDataset::OpenStatic;
     953        1221 :     poDriver->pfnIdentify = STACITDataset::Identify;
     954             : 
     955        1221 :     GetGDALDriverManager()->RegisterDriver(poDriver);
     956             : }

Generated by: LCOV version 1.14