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