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

Generated by: LCOV version 1.14