LCOV - code coverage report
Current view: top level - frmts/eeda - eedacommon.cpp (source / functions) Hit Total Coverage
Test: gdal_filtered.info Lines: 167 230 72.6 %
Date: 2024-11-21 22:18:42 Functions: 6 8 75.0 %

          Line data    Source code
       1             : /******************************************************************************
       2             :  *
       3             :  * Project:  Earth Engine Data API Images driver
       4             :  * Purpose:  Earth Engine Data API Images driver
       5             :  * Author:   Even Rouault, even dot rouault at spatialys.com
       6             :  *
       7             :  ******************************************************************************
       8             :  * Copyright (c) 2017-2018, Planet Labs
       9             :  *
      10             :  * SPDX-License-Identifier: MIT
      11             :  ****************************************************************************/
      12             : 
      13             : #include "cpl_http.h"
      14             : #include "eeda.h"
      15             : #include "ogrlibjsonutils.h"
      16             : 
      17             : #include <stdlib.h>
      18             : #include <limits>
      19             : 
      20             : std::vector<EEDAIBandDesc>
      21          10 : BuildBandDescArray(json_object *poBands,
      22             :                    std::map<CPLString, CPLString> &oMapCodeToWKT)
      23             : {
      24          10 :     const auto nBandCount = json_object_array_length(poBands);
      25          10 :     std::vector<EEDAIBandDesc> aoBandDesc;
      26             : 
      27          34 :     for (auto i = decltype(nBandCount){0}; i < nBandCount; i++)
      28             :     {
      29          24 :         json_object *poBand = json_object_array_get_idx(poBands, i);
      30          48 :         if (poBand == nullptr ||
      31          24 :             json_object_get_type(poBand) != json_type_object)
      32           0 :             continue;
      33             : 
      34          24 :         json_object *poId = CPL_json_object_object_get(poBand, "id");
      35          24 :         const char *pszBandId = json_object_get_string(poId);
      36          24 :         if (pszBandId == nullptr)
      37           0 :             continue;
      38             : 
      39             :         json_object *poDataType =
      40          24 :             CPL_json_object_object_get(poBand, "dataType");
      41          48 :         if (poDataType == nullptr ||
      42          24 :             json_object_get_type(poDataType) != json_type_object)
      43             :         {
      44           0 :             continue;
      45             :         }
      46             : 
      47             :         json_object *poPrecision =
      48          24 :             CPL_json_object_object_get(poDataType, "precision");
      49          24 :         const char *pszPrecision = json_object_get_string(poPrecision);
      50          24 :         if (pszPrecision == nullptr)
      51           0 :             continue;
      52          24 :         GDALDataType eDT = GDT_Byte;
      53          24 :         if (EQUAL(pszPrecision, "INT"))
      54             :         {
      55             :             json_object *poRange =
      56          24 :                 CPL_json_object_object_get(poDataType, "range");
      57          24 :             if (poRange && json_object_get_type(poRange) == json_type_object)
      58             :             {
      59          24 :                 int nMin = 0;
      60          24 :                 int nMax = 0;
      61          24 :                 json_object *poMin = CPL_json_object_object_get(poRange, "min");
      62          24 :                 if (poMin)
      63             :                 {
      64           0 :                     nMin = json_object_get_int(poMin);
      65             :                 }
      66          24 :                 json_object *poMax = CPL_json_object_object_get(poRange, "max");
      67          24 :                 if (poMax)
      68             :                 {
      69          24 :                     nMax = json_object_get_int(poMax);
      70             :                 }
      71             : 
      72          24 :                 if (nMin == -128 && nMax == 127)
      73             :                 {
      74           0 :                     eDT = GDT_Int8;
      75             :                 }
      76          24 :                 else if (nMin < std::numeric_limits<GInt16>::min())
      77             :                 {
      78           0 :                     eDT = GDT_Int32;
      79             :                 }
      80          24 :                 else if (nMax > std::numeric_limits<GUInt16>::max())
      81             :                 {
      82           0 :                     eDT = GDT_UInt32;
      83             :                 }
      84          24 :                 else if (nMin < 0)
      85             :                 {
      86           0 :                     eDT = GDT_Int16;
      87             :                 }
      88          24 :                 else if (nMax > std::numeric_limits<GByte>::max())
      89             :                 {
      90          19 :                     eDT = GDT_UInt16;
      91             :                 }
      92             :             }
      93             :         }
      94           0 :         else if (EQUAL(pszPrecision, "FLOAT"))
      95             :         {
      96           0 :             eDT = GDT_Float32;
      97             :         }
      98           0 :         else if (EQUAL(pszPrecision, "DOUBLE"))
      99             :         {
     100           0 :             eDT = GDT_Float64;
     101             :         }
     102             :         else
     103             :         {
     104           0 :             CPLError(CE_Warning, CPLE_NotSupported,
     105             :                      "Unhandled dataType %s for band %s", pszPrecision,
     106             :                      pszBandId);
     107           0 :             continue;
     108             :         }
     109             : 
     110          24 :         json_object *poGrid = CPL_json_object_object_get(poBand, "grid");
     111          48 :         if (poGrid == nullptr ||
     112          24 :             json_object_get_type(poGrid) != json_type_object)
     113             :         {
     114           0 :             continue;
     115             :         }
     116             : 
     117          24 :         CPLString osWKT;
     118             :         // Cf https://developers.google.com/earth-engine/reference/rest/v1alpha/PixelGrid
     119          24 :         json_object *poCrs = CPL_json_object_object_get(poGrid, "crsCode");
     120          24 :         if (poCrs == nullptr)
     121           0 :             poCrs = CPL_json_object_object_get(poGrid, "crsWkt");
     122          24 :         if (poCrs ==
     123             :             nullptr)  // "wkt" must come from a preliminary version of the API
     124           0 :             poCrs = CPL_json_object_object_get(poGrid, "wkt");
     125          24 :         OGRSpatialReference oSRS;
     126          24 :         if (poCrs)
     127             :         {
     128          24 :             const char *pszStr = json_object_get_string(poCrs);
     129          24 :             if (pszStr == nullptr)
     130           0 :                 continue;
     131          24 :             if (STARTS_WITH(pszStr, "SR-ORG:"))
     132             :             {
     133             :                 // For EEDA:MCD12Q1 for example
     134             :                 pszStr =
     135           0 :                     CPLSPrintf("http://spatialreference.org/ref/sr-org/%s/",
     136             :                                pszStr + strlen("SR-ORG:"));
     137             :             }
     138             : 
     139             :             std::map<CPLString, CPLString>::const_iterator oIter =
     140          24 :                 oMapCodeToWKT.find(pszStr);
     141          24 :             if (oIter != oMapCodeToWKT.end())
     142             :             {
     143          15 :                 osWKT = oIter->second;
     144             :             }
     145           9 :             else if (oSRS.SetFromUserInput(pszStr) != OGRERR_NONE)
     146             :             {
     147           0 :                 CPLError(CE_Warning, CPLE_AppDefined, "Unrecognized crs: %s",
     148             :                          pszStr);
     149           0 :                 oMapCodeToWKT[pszStr] = "";
     150             :             }
     151             :             else
     152             :             {
     153           9 :                 char *pszWKT = nullptr;
     154           9 :                 oSRS.exportToWkt(&pszWKT);
     155           9 :                 if (pszWKT != nullptr)
     156           9 :                     osWKT = pszWKT;
     157           9 :                 CPLFree(pszWKT);
     158           9 :                 oMapCodeToWKT[pszStr] = osWKT;
     159             :             }
     160             :         }
     161             : 
     162             :         json_object *poAT =
     163          24 :             CPL_json_object_object_get(poGrid, "affineTransform");
     164          24 :         if (poAT == nullptr || json_object_get_type(poAT) != json_type_object)
     165             :         {
     166           0 :             continue;
     167             :         }
     168             :         std::vector<double> adfGeoTransform{
     169          48 :             json_object_get_double(
     170          24 :                 CPL_json_object_object_get(poAT, "translateX")),
     171          24 :             json_object_get_double(CPL_json_object_object_get(poAT, "scaleX")),
     172          24 :             json_object_get_double(CPL_json_object_object_get(poAT, "shearX")),
     173          48 :             json_object_get_double(
     174          24 :                 CPL_json_object_object_get(poAT, "translateY")),
     175          24 :             json_object_get_double(CPL_json_object_object_get(poAT, "shearY")),
     176          24 :             json_object_get_double(CPL_json_object_object_get(poAT, "scaleY")),
     177         144 :         };
     178             : 
     179             :         json_object *poDimensions =
     180          24 :             CPL_json_object_object_get(poGrid, "dimensions");
     181          48 :         if (poDimensions == nullptr ||
     182          24 :             json_object_get_type(poDimensions) != json_type_object)
     183             :         {
     184           0 :             continue;
     185             :         }
     186             :         json_object *poWidth =
     187          24 :             CPL_json_object_object_get(poDimensions, "width");
     188          24 :         int nWidth = json_object_get_int(poWidth);
     189             :         json_object *poHeight =
     190          24 :             CPL_json_object_object_get(poDimensions, "height");
     191          24 :         int nHeight = json_object_get_int(poHeight);
     192             : 
     193             : #if 0
     194             :         if( poWidth == nullptr && poHeight == nullptr && poX == nullptr && poY == nullptr &&
     195             :             dfResX == 1.0 && dfResY == 1.0 )
     196             :         {
     197             :             // e.g. EEDAI:LT5_L1T_8DAY_EVI/19840109
     198             :             const char* pszAuthorityName = oSRS.GetAuthorityName(nullptr);
     199             :             const char* pszAuthorityCode = oSRS.GetAuthorityCode(nullptr);
     200             :             if( pszAuthorityName && pszAuthorityCode &&
     201             :                 EQUAL(pszAuthorityName, "EPSG") &&
     202             :                 EQUAL(pszAuthorityCode, "4326") )
     203             :             {
     204             :                 dfX = -180;
     205             :                 dfY = 90;
     206             :                 nWidth = 1 << 30;
     207             :                 nHeight = 1 << 29;
     208             :                 dfResX = 360.0 / nWidth;
     209             :                 dfResY = -dfResX;
     210             :             }
     211             :         }
     212             : #endif
     213             : 
     214          24 :         if (nWidth <= 0 || nHeight <= 0)
     215             :         {
     216           0 :             CPLError(CE_Warning, CPLE_AppDefined,
     217             :                      "Invalid width/height for band %s", pszBandId);
     218           0 :             continue;
     219             :         }
     220             : 
     221          48 :         EEDAIBandDesc oDesc;
     222          24 :         oDesc.osName = pszBandId;
     223          24 :         oDesc.osWKT = std::move(osWKT);
     224          24 :         oDesc.eDT = eDT;
     225          24 :         oDesc.adfGeoTransform = std::move(adfGeoTransform);
     226          24 :         oDesc.nWidth = nWidth;
     227          24 :         oDesc.nHeight = nHeight;
     228          24 :         aoBandDesc.emplace_back(std::move(oDesc));
     229             :     }
     230          10 :     return aoBandDesc;
     231             : }
     232             : 
     233             : /************************************************************************/
     234             : /*                      GDALEEDABaseDataset()                           */
     235             : /************************************************************************/
     236             : 
     237          43 : GDALEEDABaseDataset::GDALEEDABaseDataset()
     238          43 :     : m_bMustCleanPersistent(false), m_nExpirationTime(0)
     239             : {
     240          43 : }
     241             : 
     242             : /************************************************************************/
     243             : /*                     ~GDALEEDABaseDataset()                           */
     244             : /************************************************************************/
     245             : 
     246          43 : GDALEEDABaseDataset::~GDALEEDABaseDataset()
     247             : {
     248          43 :     if (m_bMustCleanPersistent)
     249             :     {
     250          15 :         char **papszOptions = CSLSetNameValue(nullptr, "CLOSE_PERSISTENT",
     251             :                                               CPLSPrintf("EEDAI:%p", this));
     252          15 :         CPLHTTPDestroyResult(CPLHTTPFetch(m_osBaseURL, papszOptions));
     253          15 :         CSLDestroy(papszOptions);
     254             :     }
     255          43 : }
     256             : 
     257             : /************************************************************************/
     258             : /*                          ConvertPathToName()                        */
     259             : /************************************************************************/
     260             : 
     261          16 : CPLString GDALEEDABaseDataset::ConvertPathToName(const CPLString &path)
     262             : {
     263          16 :     size_t end = path.find('/');
     264          32 :     CPLString folder = path.substr(0, end);
     265             : 
     266          16 :     if (folder == "users")
     267             :     {
     268           2 :         return "projects/earthengine-legacy/assets/" + path;
     269             :     }
     270          15 :     else if (folder != "projects")
     271             :     {
     272          24 :         return "projects/earthengine-public/assets/" + path;
     273             :     }
     274             : 
     275             :     // Find the start and end positions of the third segment, if it exists.
     276           3 :     int segment = 1;
     277           3 :     size_t start = 0;
     278           8 :     while (end != std::string::npos && segment < 3)
     279             :     {
     280           5 :         segment++;
     281           5 :         start = end + 1;
     282           5 :         end = path.find('/', start);
     283             :     }
     284             : 
     285           3 :     end = (end == std::string::npos) ? path.size() : end;
     286             :     // segment is 3 if path has at least 3 segments.
     287           3 :     if (folder == "projects" && segment == 3)
     288             :     {
     289             :         // If the first segment is "projects" and the third segment is "assets",
     290             :         // path is a name, so return as-is.
     291           2 :         if (path.substr(start, end - start) == "assets")
     292             :         {
     293           1 :             return path;
     294             :         }
     295             :     }
     296           4 :     return "projects/earthengine-legacy/assets/" + path;
     297             : }
     298             : 
     299             : /************************************************************************/
     300             : /*                          GetBaseHTTPOptions()                        */
     301             : /************************************************************************/
     302             : 
     303          27 : char **GDALEEDABaseDataset::GetBaseHTTPOptions()
     304             : {
     305          27 :     m_bMustCleanPersistent = true;
     306             : 
     307          27 :     char **papszOptions = nullptr;
     308             :     papszOptions =
     309          27 :         CSLAddString(papszOptions, CPLSPrintf("PERSISTENT=EEDAI:%p", this));
     310             : 
     311             :     // Strategy to get the Bearer Authorization value:
     312             :     // - if it is specified in the EEDA_BEARER config option, use it
     313             :     // - otherwise if EEDA_BEARER_FILE is specified, read it and use its content
     314             :     // - otherwise if GOOGLE_APPLICATION_CREDENTIALS is specified, read the
     315             :     //   corresponding file to get the private key and client_email, to get a
     316             :     //   bearer using OAuth2ServiceAccount method
     317             :     // - otherwise if EEDA_PRIVATE_KEY and EEDA_CLIENT_EMAIL are set, use them
     318             :     //   to get a bearer using OAuth2ServiceAccount method
     319             :     // - otherwise if EEDA_PRIVATE_KEY_FILE and EEDA_CLIENT_EMAIL are set, use
     320             :     //   them to get a bearer
     321             : 
     322          54 :     CPLString osBearer(CPLGetConfigOption("EEDA_BEARER", m_osBearer));
     323          50 :     if (osBearer.empty() ||
     324          23 :         (!m_osBearer.empty() && time(nullptr) > m_nExpirationTime))
     325             :     {
     326           4 :         CPLString osBearerFile(CPLGetConfigOption("EEDA_BEARER_FILE", ""));
     327           4 :         if (!osBearerFile.empty())
     328             :         {
     329           0 :             VSILFILE *fp = VSIFOpenL(osBearerFile, "rb");
     330           0 :             if (fp == nullptr)
     331             :             {
     332           0 :                 CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
     333             :                          osBearerFile.c_str());
     334             :             }
     335             :             else
     336             :             {
     337             :                 char abyBuffer[512];
     338           0 :                 size_t nRead = VSIFReadL(abyBuffer, 1, sizeof(abyBuffer), fp);
     339           0 :                 osBearer.assign(abyBuffer, nRead);
     340           0 :                 VSIFCloseL(fp);
     341             :             }
     342             :         }
     343             :         else
     344             :         {
     345           4 :             CPLString osPrivateKey(CPLGetConfigOption("EEDA_PRIVATE_KEY", ""));
     346             :             CPLString osClientEmail(
     347           4 :                 CPLGetConfigOption("EEDA_CLIENT_EMAIL", ""));
     348             : 
     349           4 :             if (osPrivateKey.empty())
     350             :             {
     351             :                 CPLString osPrivateKeyFile(
     352           6 :                     CPLGetConfigOption("EEDA_PRIVATE_KEY_FILE", ""));
     353           3 :                 if (!osPrivateKeyFile.empty())
     354             :                 {
     355           0 :                     VSILFILE *fp = VSIFOpenL(osPrivateKeyFile, "rb");
     356           0 :                     if (fp == nullptr)
     357             :                     {
     358           0 :                         CPLError(CE_Failure, CPLE_FileIO, "Cannot open %s",
     359             :                                  osPrivateKeyFile.c_str());
     360             :                     }
     361             :                     else
     362             :                     {
     363             :                         char *pabyBuffer =
     364           0 :                             static_cast<char *>(CPLMalloc(32768));
     365           0 :                         size_t nRead = VSIFReadL(pabyBuffer, 1, 32768, fp);
     366           0 :                         osPrivateKey.assign(pabyBuffer, nRead);
     367           0 :                         VSIFCloseL(fp);
     368           0 :                         CPLFree(pabyBuffer);
     369             :                     }
     370             :                 }
     371             :             }
     372             : 
     373           4 :             CPLString osServiceAccountJson;
     374             :             const char *pszVSIPath =
     375           4 :                 CSLFetchNameValue(papszOpenOptions, "VSI_PATH_FOR_AUTH");
     376           4 :             if (pszVSIPath)
     377             :                 osServiceAccountJson = VSIGetPathSpecificOption(
     378           0 :                     pszVSIPath, "GOOGLE_APPLICATION_CREDENTIALS", "");
     379           4 :             if (osServiceAccountJson.empty())
     380             :                 osServiceAccountJson =
     381           4 :                     CPLGetConfigOption("GOOGLE_APPLICATION_CREDENTIALS", "");
     382           4 :             if (!osServiceAccountJson.empty())
     383             :             {
     384           1 :                 CPLJSONDocument oDoc;
     385           1 :                 if (!oDoc.Load(osServiceAccountJson))
     386             :                 {
     387           0 :                     CSLDestroy(papszOptions);
     388           0 :                     return nullptr;
     389             :                 }
     390             : 
     391           1 :                 osPrivateKey = oDoc.GetRoot().GetString("private_key");
     392           1 :                 osPrivateKey.replaceAll("\\n", "\n");
     393           1 :                 osClientEmail = oDoc.GetRoot().GetString("client_email");
     394             :             }
     395             : 
     396           4 :             char **papszMD = nullptr;
     397           4 :             if (!osPrivateKey.empty() && !osClientEmail.empty())
     398             :             {
     399           2 :                 CPLDebug("EEDA", "Requesting Bearer token");
     400           2 :                 osPrivateKey.replaceAll("\\n", "\n");
     401             :                 // CPLDebug("EEDA", "Private key: %s", osPrivateKey.c_str());
     402           2 :                 papszMD = GOA2GetAccessTokenFromServiceAccount(
     403             :                     osPrivateKey, osClientEmail,
     404             :                     "https://www.googleapis.com/auth/earthengine.readonly",
     405             :                     nullptr, nullptr);
     406           2 :                 if (papszMD == nullptr)
     407             :                 {
     408           0 :                     CSLDestroy(papszOptions);
     409           0 :                     return nullptr;
     410             :                 }
     411             :             }
     412             :             // Some Travis-CI workers are GCE machines, and for some tests, we
     413             :             // don't want this code path to be taken. And on AppVeyor/Window, we
     414             :             // would also attempt a network access
     415           4 :             else if (!CPLTestBool(CPLGetConfigOption("CPL_GCE_SKIP", "NO")) &&
     416           2 :                      CPLIsMachinePotentiallyGCEInstance())
     417             :             {
     418           1 :                 papszMD = GOA2GetAccessTokenFromCloudEngineVM(nullptr);
     419             :             }
     420             : 
     421           4 :             if (papszMD)
     422             :             {
     423           3 :                 osBearer = CSLFetchNameValueDef(papszMD, "access_token", "");
     424           3 :                 m_osBearer = osBearer;
     425           3 :                 m_nExpirationTime = CPLAtoGIntBig(
     426             :                     CSLFetchNameValueDef(papszMD, "expires_in", "0"));
     427           3 :                 if (m_nExpirationTime != 0)
     428           3 :                     m_nExpirationTime += time(nullptr) - 10;
     429           3 :                 CSLDestroy(papszMD);
     430             :             }
     431             :             else
     432             :             {
     433           1 :                 CPLError(CE_Failure, CPLE_AppDefined,
     434             :                          "Missing EEDA_BEARER, EEDA_BEARER_FILE or "
     435             :                          "GOOGLE_APPLICATION_CREDENTIALS or "
     436             :                          "EEDA_PRIVATE_KEY/EEDA_PRIVATE_KEY_FILE + "
     437             :                          "EEDA_CLIENT_EMAIL config option or "
     438             :                          "VSI_PATH_FOR_AUTH open option");
     439           1 :                 CSLDestroy(papszOptions);
     440           1 :                 return nullptr;
     441             :             }
     442             :         }
     443             :     }
     444          26 :     papszOptions = CSLAddString(
     445             :         papszOptions,
     446             :         CPLSPrintf("HEADERS=Authorization: Bearer %s", osBearer.c_str()));
     447             : 
     448          26 :     return papszOptions;
     449             : }
     450             : 
     451             : /* Add a small amount of random jitter to avoid cyclic server stampedes */
     452           0 : static double EEDABackoffFactor(double base)
     453             : {
     454             :     // coverity[dont_call]
     455           0 :     return base + rand() * 0.5 / RAND_MAX;
     456             : }
     457             : 
     458             : /************************************************************************/
     459             : /*                           EEDAHTTPFetch()                            */
     460             : /************************************************************************/
     461             : 
     462          26 : CPLHTTPResult *EEDAHTTPFetch(const char *pszURL, char **papszOptions)
     463             : {
     464             :     CPLHTTPResult *psResult;
     465          26 :     const int RETRY_COUNT = 4;
     466          26 :     double dfRetryDelay = 1.0;
     467          26 :     for (int i = 0; i <= RETRY_COUNT; i++)
     468             :     {
     469          26 :         psResult = CPLHTTPFetch(pszURL, papszOptions);
     470             : 
     471          26 :         if (psResult == nullptr)
     472           0 :             break;
     473          26 :         if (psResult->nDataLen != 0 && psResult->nStatus == 0 &&
     474          26 :             psResult->pszErrBuf == nullptr)
     475             :         {
     476             :             /* got a valid response */
     477          26 :             CPLErrorReset();
     478          26 :             break;
     479             :         }
     480             :         else
     481             :         {
     482           0 :             const char *pszErrorText =
     483           0 :                 psResult->pszErrBuf ? psResult->pszErrBuf : "(null)";
     484             : 
     485             :             /* Get HTTP status code */
     486           0 :             int nHTTPStatus = -1;
     487           0 :             if (psResult->pszErrBuf != nullptr &&
     488           0 :                 EQUALN(psResult->pszErrBuf,
     489             :                        "HTTP error code : ", strlen("HTTP error code : ")))
     490             :             {
     491             :                 nHTTPStatus =
     492           0 :                     atoi(psResult->pszErrBuf + strlen("HTTP error code : "));
     493           0 :                 if (psResult->pabyData)
     494           0 :                     pszErrorText =
     495             :                         reinterpret_cast<const char *>(psResult->pabyData);
     496             :             }
     497             : 
     498           0 :             if ((nHTTPStatus == 429 || nHTTPStatus == 500 ||
     499           0 :                  (nHTTPStatus >= 502 && nHTTPStatus <= 504)) &&
     500             :                 i < RETRY_COUNT)
     501             :             {
     502           0 :                 CPLError(CE_Warning, CPLE_FileIO,
     503             :                          "GET error when downloading %s, HTTP status=%d, "
     504             :                          "retrying in %.2fs : %s",
     505             :                          pszURL, nHTTPStatus, dfRetryDelay, pszErrorText);
     506           0 :                 CPLHTTPDestroyResult(psResult);
     507           0 :                 psResult = nullptr;
     508             : 
     509           0 :                 CPLSleep(dfRetryDelay);
     510           0 :                 dfRetryDelay *= EEDABackoffFactor(4);
     511             :             }
     512             :             else
     513             :             {
     514             :                 break;
     515             :             }
     516             :         }
     517             :     }
     518             : 
     519          26 :     return psResult;
     520             : }

Generated by: LCOV version 1.14