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

Generated by: LCOV version 1.14