1 /******************************************************************************
2  *
3  * Project:  PlanetLabs scene driver
4  * Purpose:  Implements OGRPLScenesDataV1Dataset
5  * Author:   Even Rouault, even dot rouault at spatialys.com
6  *
7  ******************************************************************************
8  * Copyright (c) 2016, 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 "ogr_plscenes.h"
30 #include "ogrgeojsonreader.h"
31 #include <time.h>
32 
33 CPL_CVSID("$Id: ogrplscenesdatav1dataset.cpp 805c0f42527c4129db9ff7299677426005fa12fc 2021-06-21 18:02:56 +0200 Even Rouault $")
34 
35 /************************************************************************/
36 /*                       OGRPLScenesDataV1Dataset()                     */
37 /************************************************************************/
38 
OGRPLScenesDataV1Dataset()39 OGRPLScenesDataV1Dataset::OGRPLScenesDataV1Dataset() :
40     m_bLayerListInitialized(false),
41     m_bMustCleanPersistent(false),
42     m_nLayers(0),
43     m_papoLayers(nullptr),
44     m_bFollowLinks(false)
45 {}
46 
47 /************************************************************************/
48 /*                       ~OGRPLScenesDataV1Dataset()                    */
49 /************************************************************************/
50 
~OGRPLScenesDataV1Dataset()51 OGRPLScenesDataV1Dataset::~OGRPLScenesDataV1Dataset()
52 {
53     for( int i = 0; i < m_nLayers; i++ )
54         delete m_papoLayers[i];
55     CPLFree(m_papoLayers);
56 
57     if( m_bMustCleanPersistent )
58     {
59         char **papszOptions =
60             CSLSetNameValue(
61                 nullptr, "CLOSE_PERSISTENT", CPLSPrintf("PLSCENES:%p", this));
62         CPLHTTPDestroyResult(CPLHTTPFetch(m_osBaseURL, papszOptions));
63         CSLDestroy(papszOptions);
64     }
65 }
66 
67 /************************************************************************/
68 /*                              GetLayer()                              */
69 /************************************************************************/
70 
GetLayer(int idx)71 OGRLayer *OGRPLScenesDataV1Dataset::GetLayer(int idx)
72 {
73     if( idx < 0 || idx >= GetLayerCount() )
74         return nullptr;
75     return m_papoLayers[idx];
76 }
77 
78 /************************************************************************/
79 /*                           GetLayerCount()                            */
80 /************************************************************************/
81 
GetLayerCount()82 int OGRPLScenesDataV1Dataset::GetLayerCount()
83 {
84     if( !m_bLayerListInitialized )
85     {
86         m_bLayerListInitialized = true;
87         EstablishLayerList();
88     }
89     return m_nLayers;
90 }
91 
92 /************************************************************************/
93 /*                          ParseItemType()                             */
94 /************************************************************************/
95 
ParseItemType(json_object * poItemType)96 OGRLayer* OGRPLScenesDataV1Dataset::ParseItemType(json_object* poItemType)
97 {
98     if( poItemType == nullptr || json_object_get_type(poItemType) != json_type_object )
99         return nullptr;
100     json_object* poId = CPL_json_object_object_get(poItemType, "id");
101     if( poId == nullptr || json_object_get_type(poId) != json_type_string )
102         return nullptr;
103 
104     CPLString osDisplayDescription;
105     json_object* poDisplayDescription = CPL_json_object_object_get(poItemType, "display_description");
106     if( poDisplayDescription != nullptr && json_object_get_type(poDisplayDescription) == json_type_string )
107         osDisplayDescription = json_object_get_string(poDisplayDescription);
108     CPLString osDisplayName;
109     json_object* poDisplayName = CPL_json_object_object_get(poItemType, "display_name");
110     if( poDisplayName != nullptr && json_object_get_type(poDisplayName) == json_type_string )
111         osDisplayName = json_object_get_string(poDisplayName);
112 
113     const char* pszId = json_object_get_string(poId);
114 
115     // The layer might already exist if GetLayerByName() is called before
116     // GetLayer()/GetLayerCount() is
117 
118     // Prevent GetLayerCount() from calling EstablishLayerList()
119     bool bLayerListInitializedBackup = m_bLayerListInitialized;
120     m_bLayerListInitialized = true;
121     OGRLayer* poExistingLayer = GDALDataset::GetLayerByName(pszId);
122     m_bLayerListInitialized = bLayerListInitializedBackup;
123     if( poExistingLayer != nullptr )
124         return poExistingLayer;
125 
126     OGRPLScenesDataV1Layer* poPLLayer = new OGRPLScenesDataV1Layer(
127                                                                 this, pszId);
128     if( !osDisplayName.empty() )
129         poPLLayer->SetMetadataItem("SHORT_DESCRIPTION", osDisplayName.c_str());
130     if( !osDisplayDescription.empty() )
131         poPLLayer->SetMetadataItem("DESCRIPTION", osDisplayDescription.c_str());
132     m_papoLayers = (OGRPLScenesDataV1Layer**) CPLRealloc(m_papoLayers,
133                                 sizeof(OGRPLScenesDataV1Layer*) * (m_nLayers + 1));
134     m_papoLayers[m_nLayers ++] = poPLLayer;
135     return poPLLayer;
136 }
137 
138 /************************************************************************/
139 /*                          ParseItemTypes()                         */
140 /************************************************************************/
141 
ParseItemTypes(json_object * poObj,CPLString & osNext)142 bool OGRPLScenesDataV1Dataset::ParseItemTypes(json_object* poObj,
143                                              CPLString& osNext)
144 {
145     json_object* poItemTypes = CPL_json_object_object_get(poObj, "item_types");
146     if( poItemTypes == nullptr || json_object_get_type(poItemTypes) != json_type_array )
147     {
148         CPLError(CE_Failure, CPLE_AppDefined,
149                 "Missing item_types object, or not of type array");
150         return false;
151     }
152     const auto nCatalogsLength = json_object_array_length(poItemTypes);
153     for( auto i=decltype(nCatalogsLength){0}; i<nCatalogsLength; i++ )
154     {
155         json_object* poItemType = json_object_array_get_idx(poItemTypes, i);
156         ParseItemType(poItemType);
157     }
158 
159     // Is there a next page ?
160     osNext = "";
161     json_object* poLinks = CPL_json_object_object_get(poObj, "_links");
162     if( poLinks && json_object_get_type(poLinks) == json_type_object )
163     {
164         json_object* poNext = CPL_json_object_object_get(poLinks, "_next");
165         if( poNext && json_object_get_type(poNext) == json_type_string )
166         {
167             osNext = json_object_get_string(poNext);
168         }
169     }
170 
171     return true;
172 }
173 
174 /************************************************************************/
175 /*                          EstablishLayerList()                        */
176 /************************************************************************/
177 
EstablishLayerList()178 void OGRPLScenesDataV1Dataset::EstablishLayerList()
179 {
180     CPLString osURL(m_osNextItemTypesPageURL);
181     m_osNextItemTypesPageURL = "";
182 
183     while( !osURL.empty() )
184     {
185         json_object* poObj = RunRequest(osURL);
186         if( poObj == nullptr )
187             break;
188         if( !ParseItemTypes( poObj, osURL ) )
189         {
190             json_object_put(poObj);
191             break;
192         }
193         json_object_put(poObj);
194     }
195 }
196 
197 /************************************************************************/
198 /*                          GetLayerByName()                            */
199 /************************************************************************/
200 
GetLayerByName(const char * pszName)201 OGRLayer *OGRPLScenesDataV1Dataset::GetLayerByName(const char* pszName)
202 {
203     // Prevent GetLayerCount() from calling EstablishLayerList()
204     bool bLayerListInitializedBackup = m_bLayerListInitialized;
205     m_bLayerListInitialized = true;
206     OGRLayer* poRet = GDALDataset::GetLayerByName(pszName);
207     m_bLayerListInitialized = bLayerListInitializedBackup;
208     if( poRet != nullptr )
209         return poRet;
210 
211     CPLString osURL(m_osBaseURL + "item-types/" + pszName);
212     json_object* poObj = RunRequest(osURL);
213     if( poObj == nullptr )
214         return nullptr;
215     poRet = ParseItemType(poObj);
216     json_object_put(poObj);
217     return poRet;
218 }
219 
220 /************************************************************************/
221 /*                          GetBaseHTTPOptions()                         */
222 /************************************************************************/
223 
GetBaseHTTPOptions()224 char** OGRPLScenesDataV1Dataset::GetBaseHTTPOptions()
225 {
226     m_bMustCleanPersistent = true;
227 
228     char** papszOptions = nullptr;
229     papszOptions =
230         CSLAddString(papszOptions, CPLSPrintf("PERSISTENT=PLSCENES:%p", this));
231     papszOptions =
232         CSLAddString(papszOptions,
233                      CPLSPrintf("HEADERS=Authorization: api-key %s",
234                                 m_osAPIKey.c_str()));
235 
236     return papszOptions;
237 }
238 
239 /************************************************************************/
240 /*                               RunRequest()                           */
241 /************************************************************************/
242 
RunRequest(const char * pszURL,int bQuiet404Error,const char * pszHTTPVerb,bool bExpectJSonReturn,const char * pszPostContent)243 json_object* OGRPLScenesDataV1Dataset::RunRequest(const char* pszURL,
244                                               int bQuiet404Error,
245                                               const char* pszHTTPVerb,
246                                               bool bExpectJSonReturn,
247                                               const char* pszPostContent)
248 {
249     char** papszOptions = CSLAddString(GetBaseHTTPOptions(), nullptr);
250     // We need to set it each time as CURL would reuse the previous value
251     // if reusing the same connection
252     papszOptions = CSLSetNameValue(papszOptions, "CUSTOMREQUEST", pszHTTPVerb);
253     if( pszPostContent != nullptr )
254     {
255         CPLString osHeaders = CSLFetchNameValueDef(papszOptions, "HEADERS", "");
256         if( !osHeaders.empty() )
257             osHeaders += "\r\n";
258         osHeaders += "Content-Type: application/json";
259         papszOptions = CSLSetNameValue(papszOptions, "HEADERS", osHeaders);
260         papszOptions = CSLSetNameValue(papszOptions, "POSTFIELDS", pszPostContent);
261     }
262     papszOptions = CSLSetNameValue(papszOptions, "MAX_RETRY", "3");
263     CPLHTTPResult *psResult = nullptr;
264     if( STARTS_WITH(m_osBaseURL, "/vsimem/") &&
265         STARTS_WITH(pszURL, "/vsimem/") )
266     {
267         psResult = (CPLHTTPResult*) CPLCalloc(1, sizeof(CPLHTTPResult));
268         vsi_l_offset nDataLengthLarge = 0;
269         CPLString osURL(pszURL);
270         if( osURL[osURL.size()-1 ] == '/' )
271             osURL.resize(osURL.size()-1);
272         if( pszPostContent != nullptr )
273         {
274             osURL += "&POSTFIELDS=";
275             osURL += pszPostContent;
276         }
277         CPLDebug("PLSCENES", "Fetching %s", osURL.c_str());
278         GByte* pabyBuf = VSIGetMemFileBuffer(osURL, &nDataLengthLarge, FALSE);
279         size_t nDataLength = static_cast<size_t>(nDataLengthLarge);
280         if( pabyBuf )
281         {
282             psResult->pabyData = (GByte*) VSI_MALLOC_VERBOSE(1 + nDataLength);
283             if( psResult->pabyData )
284             {
285                 memcpy(psResult->pabyData, pabyBuf, nDataLength);
286                 psResult->pabyData[nDataLength] = 0;
287             }
288         }
289         else
290         {
291             psResult->pszErrBuf =
292                 CPLStrdup(CPLSPrintf("Error 404. Cannot find %s", osURL.c_str()));
293         }
294     }
295     else
296     {
297         if( bQuiet404Error )
298             CPLPushErrorHandler(CPLQuietErrorHandler);
299         psResult = CPLHTTPFetch( pszURL, papszOptions);
300         if( bQuiet404Error )
301             CPLPopErrorHandler();
302     }
303     CSLDestroy(papszOptions);
304 
305     if( pszPostContent != nullptr && m_bMustCleanPersistent )
306     {
307         papszOptions = CSLSetNameValue(nullptr, "CLOSE_PERSISTENT", CPLSPrintf("PLSCENES:%p", this));
308         CPLHTTPDestroyResult(CPLHTTPFetch(m_osBaseURL, papszOptions));
309         CSLDestroy(papszOptions);
310         m_bMustCleanPersistent = false;
311     }
312 
313     if( psResult->pszErrBuf != nullptr )
314     {
315         if( !(bQuiet404Error && strstr(psResult->pszErrBuf, "404")) )
316         {
317             CPLError(CE_Failure, CPLE_AppDefined, "%s",
318                     psResult->pabyData ? (const char*) psResult->pabyData :
319                     psResult->pszErrBuf);
320         }
321         CPLHTTPDestroyResult(psResult);
322         return nullptr;
323     }
324 
325     if( !bExpectJSonReturn && (psResult->pabyData == nullptr || psResult->nDataLen == 0) )
326     {
327         CPLHTTPDestroyResult(psResult);
328         return nullptr;
329     }
330 
331     if( psResult->pabyData == nullptr )
332     {
333         CPLError(CE_Failure, CPLE_AppDefined, "Empty content returned by server");
334         CPLHTTPDestroyResult(psResult);
335         return nullptr;
336     }
337 
338     const char* pszText = reinterpret_cast<const char*>(psResult->pabyData);
339 #ifdef DEBUG_VERBOSE
340     CPLDebug("PLScenes", "%s", pszText);
341 #endif
342 
343     json_object* poObj = nullptr;
344     if( !OGRJSonParse(pszText, &poObj, true) )
345     {
346         CPLHTTPDestroyResult(psResult);
347         return nullptr;
348     }
349 
350     CPLHTTPDestroyResult(psResult);
351 
352     if( json_object_get_type(poObj) != json_type_object )
353     {
354         CPLError( CE_Failure, CPLE_AppDefined, "Return is not a JSON dictionary");
355         json_object_put(poObj);
356         poObj = nullptr;
357     }
358 
359     return poObj;
360 }
361 
362 /************************************************************************/
363 /*                           InsertAPIKeyInURL()                        */
364 /************************************************************************/
365 
InsertAPIKeyInURL(CPLString osURL)366 CPLString OGRPLScenesDataV1Dataset::InsertAPIKeyInURL(CPLString osURL)
367 {
368     if( STARTS_WITH(osURL, "http://") )
369     {
370         osURL = "http://" + m_osAPIKey + ":@" + osURL.substr(strlen("http://"));
371     }
372     else if( STARTS_WITH(osURL, "https://") )
373     {
374         osURL = "https://" + m_osAPIKey + ":@" + osURL.substr(strlen("https://"));
375     }
376     return osURL;
377 }
378 
379 /************************************************************************/
380 /*                            OpenRasterScene()                         */
381 /************************************************************************/
382 
OpenRasterScene(GDALOpenInfo * poOpenInfo,CPLString osScene,char ** papszOptions)383 GDALDataset* OGRPLScenesDataV1Dataset::OpenRasterScene(GDALOpenInfo* poOpenInfo,
384                                                  CPLString osScene,
385                                                  char** papszOptions)
386 {
387     if( !(poOpenInfo->nOpenFlags & GDAL_OF_RASTER) )
388     {
389         CPLError(CE_Failure, CPLE_AppDefined,
390                  "The scene option must only be used with vector access");
391         return nullptr;
392     }
393 
394     int nActivationTimeout = atoi(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
395                                                       "ACTIVATION_TIMEOUT", "3600"));
396 
397     for( char** papszIter = papszOptions; papszIter && *papszIter; papszIter ++ )
398     {
399         char* pszKey = nullptr;
400         const char* pszValue = CPLParseNameValue(*papszIter, &pszKey);
401         if( pszValue != nullptr )
402         {
403             if( !EQUAL(pszKey, "api_key") &&
404                 !EQUAL(pszKey, "scene") &&
405                 !EQUAL(pszKey, "product_type") &&
406                 !EQUAL(pszKey, "asset") &&
407                 !EQUAL(pszKey, "catalog") &&
408                 !EQUAL(pszKey, "itemtypes") &&
409                 !EQUAL(pszKey, "version") &&
410                 !EQUAL(pszKey, "follow_links") &&
411                 !EQUAL(pszKey, "metadata"))
412             {
413                 CPLError(CE_Failure, CPLE_NotSupported, "Unsupported option %s", pszKey);
414                 CPLFree(pszKey);
415                 return nullptr;
416             }
417             CPLFree(pszKey);
418         }
419     }
420 
421     const char* pszCatalog =
422         CSLFetchNameValueDef(papszOptions, "itemtypes",
423         CSLFetchNameValueDef(papszOptions, "catalog",
424         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "ITEMTYPES",
425         CSLFetchNameValue(poOpenInfo->papszOpenOptions, "CATALOG"))));
426     if( pszCatalog == nullptr )
427     {
428         CPLError(CE_Failure, CPLE_AppDefined, "Missing catalog");
429         return nullptr;
430     }
431 
432     const char* pszProductType =
433         CSLFetchNameValueDef(papszOptions, "asset",
434         CSLFetchNameValueDef(papszOptions, "product_type",
435         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "ASSET",
436         CSLFetchNameValue(poOpenInfo->papszOpenOptions, "PRODUCT_TYPE"))));
437 
438     CPLString osRasterURL;
439     osRasterURL = m_osBaseURL;
440     osRasterURL += "item-types/";
441     osRasterURL += pszCatalog;
442     osRasterURL += "/items/";
443     osRasterURL += osScene;
444     osRasterURL += "/assets/";
445 
446     time_t nStartTime = time(nullptr);
447 retry:
448     time_t nCurrentTime = time(nullptr);
449     if( nCurrentTime - nStartTime > nActivationTimeout )
450     {
451         CPLError(CE_Failure, CPLE_AppDefined, "Activation timeout reached");
452         return nullptr;
453     }
454     json_object* poObj = RunRequest( osRasterURL );
455     if( poObj == nullptr )
456         return nullptr;
457 
458     json_object* poSubObj = CPL_json_object_object_get(poObj,
459                                pszProductType ? pszProductType : "visual");
460     if( poSubObj == nullptr )
461     {
462         if( pszProductType != nullptr && !EQUAL(pszProductType, "LIST") )
463         {
464            CPLError(CE_Failure, CPLE_AppDefined, "Cannot find asset %s", pszProductType);
465            json_object_put(poObj);
466         }
467         else
468         {
469             json_object_iter it;
470             it.key = nullptr;
471             it.val = nullptr;
472             it.entry = nullptr;
473             char** papszSubdatasets = nullptr;
474             int nSubDataset = 0;
475             json_object_object_foreachC( poObj, it )
476             {
477                 ++nSubDataset;
478                 papszSubdatasets = CSLSetNameValue(papszSubdatasets,
479                         CPLSPrintf("SUBDATASET_%d_NAME", nSubDataset),
480                         CPLSPrintf("Scene=%s of item types %s, asset %s",
481                                    osScene.c_str(), pszCatalog, it.key));
482                 papszSubdatasets = CSLSetNameValue(papszSubdatasets,
483                         CPLSPrintf("SUBDATASET_%d_DESC", nSubDataset),
484                         CPLSPrintf("PLScenes:version=Data_V1,itemtypes=%s,scene=%s,asset=%s",
485                                    pszCatalog, osScene.c_str(), it.key));
486             }
487             json_object_put(poObj);
488             if( nSubDataset != 0 )
489             {
490                 GDALDataset* poDS = new OGRPLScenesDataV1Dataset();
491                 poDS->SetMetadata(papszSubdatasets, "SUBDATASETS");
492                 CSLDestroy(papszSubdatasets);
493                 return poDS;
494             }
495         }
496         return nullptr;
497     }
498 
499     if( json_object_get_type(poSubObj) != json_type_object )
500     {
501         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find link");
502         json_object_put(poObj);
503         return nullptr;
504     }
505 
506     json_object* poPermissions = CPL_json_object_object_get(poSubObj, "_permissions");
507     if( poPermissions != nullptr )
508     {
509         const char* pszPermissions = json_object_to_json_string_ext( poPermissions, 0 );
510         if( pszPermissions && strstr(pszPermissions, "download") == nullptr )
511         {
512             CPLError(CE_Warning, CPLE_AppDefined,
513                      "You don't have download permissions for this product");
514         }
515     }
516 
517     json_object* poLocation = CPL_json_object_object_get(poSubObj, "location");
518     json_object* poStatus = CPL_json_object_object_get(poSubObj, "status");
519     bool bActive = false;
520     if( poStatus != nullptr && json_object_get_type(poStatus) == json_type_string )
521     {
522         const char* pszStatus = json_object_get_string(poStatus);
523         if( EQUAL( pszStatus, "activating" ) )
524         {
525             CPLDebug("PLScenes", "The product is in activation. Retrying...");
526             CPLSleep( nActivationTimeout == 1 ? 0.5 : 1.0);
527             poLocation = nullptr;
528             json_object_put(poObj);
529             goto retry;
530         }
531         bActive = EQUAL( pszStatus, "active" );
532     }
533     if( poLocation == nullptr || json_object_get_type(poLocation) != json_type_string ||
534         !bActive )
535     {
536         CPLDebug("PLScenes", "The product isn't activated yet. Activating it");
537         json_object* poActivate = json_ex_get_object_by_path(poSubObj, "_links.activate");
538         if( poActivate == nullptr || json_object_get_type(poActivate) != json_type_string )
539         {
540             CPLError(CE_Failure, CPLE_AppDefined, "Cannot find link to activate scene %s",
541                       osScene.c_str());
542             json_object_put(poObj);
543             return nullptr;
544         }
545         CPLString osActivate = json_object_get_string(poActivate);
546         poLocation = nullptr;
547         json_object_put(poObj);
548         poObj = RunRequest( osActivate, FALSE, "GET", false );
549         if( poObj != nullptr )
550             json_object_put(poObj);
551         poObj = nullptr;
552         CPLSleep(nActivationTimeout == 1 ? 0.5 : 1.0);
553         goto retry;
554     }
555 
556     const char* pszLink = json_object_get_string(poLocation);
557 
558     osRasterURL = pszLink ? pszLink : "";
559     json_object_put(poObj);
560     if( osRasterURL.empty() )
561     {
562         CPLError(CE_Failure, CPLE_AppDefined, "Cannot find link to scene %s",
563                  osScene.c_str());
564         return nullptr;
565     }
566 
567     osRasterURL = InsertAPIKeyInURL(osRasterURL);
568 
569     const bool bUseVSICURL =
570         CPLFetchBool(poOpenInfo->papszOpenOptions, "RANDOM_ACCESS", true);
571     if( bUseVSICURL && !(STARTS_WITH(m_osBaseURL, "/vsimem/")) )
572     {
573         char* pszEscapedURL = CPLEscapeString(osRasterURL, -1, CPLES_URL);
574         CPLString osTmpURL("/vsicurl?use_head=no&max_retry=3&empty_dir=yes&use_redirect_url_if_no_query_string_params=yes&url=");
575         osTmpURL += pszEscapedURL;
576         CPLFree(pszEscapedURL);
577         CPLDebug("PLSCENES", "URL = %s", osTmpURL.c_str());
578 
579         VSIStatBufL sStat;
580         if( VSIStatL(osTmpURL, &sStat) == 0 &&
581             sStat.st_size > 0 )
582         {
583             osRasterURL = osTmpURL;
584         }
585         else
586         {
587             CPLDebug("PLSCENES", "Cannot use random access for that file");
588         }
589     }
590 
591     char** papszAllowedDrivers = nullptr;
592     papszAllowedDrivers = CSLAddString(papszAllowedDrivers, "HTTP");
593     papszAllowedDrivers = CSLAddString(papszAllowedDrivers, "GTiff");
594     papszAllowedDrivers = CSLAddString(papszAllowedDrivers, "PNG");
595     papszAllowedDrivers = CSLAddString(papszAllowedDrivers, "JPEG");
596     papszAllowedDrivers = CSLAddString(papszAllowedDrivers, "NITF");
597     papszAllowedDrivers = CSLAddString(papszAllowedDrivers, "JP2KAK");
598     papszAllowedDrivers = CSLAddString(papszAllowedDrivers, "JP2ECW");
599     papszAllowedDrivers = CSLAddString(papszAllowedDrivers, "JP2MrSID");
600     papszAllowedDrivers = CSLAddString(papszAllowedDrivers, "JP2OpenJPEG");
601     GDALDataset* poOutDS = (GDALDataset*) GDALOpenEx(osRasterURL, GDAL_OF_RASTER,
602                                                      papszAllowedDrivers, nullptr, nullptr);
603     CSLDestroy(papszAllowedDrivers);
604     if( poOutDS )
605     {
606         if( CPLFetchBool(papszOptions, "metadata",
607                 CPLFetchBool(poOpenInfo->papszOpenOptions, "METADATA", true)) )
608         {
609             OGRLayer* poLayer = GetLayerByName(pszCatalog);
610             if( poLayer != nullptr )
611             {
612                 // Set a dummy name so that PAM goes here
613                 CPLPushErrorHandler(CPLQuietErrorHandler);
614                 poOutDS->SetDescription("/vsimem/tmp/ogrplscenesDataV1");
615 
616                 /* Attach scene metadata. */
617                 poLayer->SetAttributeFilter(CPLSPrintf("id = '%s'", osScene.c_str()));
618                 OGRFeature* poFeat = poLayer->GetNextFeature();
619                 if( poFeat )
620                 {
621                     for(int i=0;i<poFeat->GetFieldCount();i++)
622                     {
623                         if( poFeat->IsFieldSetAndNotNull(i) )
624                         {
625                             const char* pszKey = poFeat->GetFieldDefnRef(i)->GetNameRef();
626                             const char* pszVal = poFeat->GetFieldAsString(i);
627                             if( strncmp(pszKey, "asset_", strlen("asset_")) == 0 ||
628                                 strstr(pszVal, "https://") != nullptr ||
629                                 strcmp(pszKey, "columns") == 0 ||
630                                 strcmp(pszKey, "rows") == 0 ||
631                                 strcmp(pszKey, "epsg_code") == 0 ||
632                                 strcmp(pszKey, "origin_x") == 0 ||
633                                 strcmp(pszKey, "origin_y") == 0 ||
634                                 strcmp(pszKey, "permissions") == 0 ||
635                                 strcmp(pszKey, "acquired") == 0 // Redundant with TIFFTAG_DATETIME
636                             )
637                             {
638                                 continue;
639                             }
640                             poOutDS->SetMetadataItem(pszKey, pszVal);
641                         }
642                     }
643                 }
644                 delete poFeat;
645 
646                 poOutDS->FlushCache();
647                 VSIUnlink("/vsimem/tmp/ogrplscenesDataV1");
648                 VSIUnlink("/vsimem/tmp/ogrplscenesDataV1.aux.xml");
649                 CPLPopErrorHandler();
650             }
651         }
652 
653         CPLErrorReset();
654         poOutDS->SetDescription(poOpenInfo->pszFilename);
655     }
656     else if( CPLGetLastErrorType() == CE_None )
657     {
658         poObj = RunRequest( osRasterURL );
659         if( poObj == nullptr )
660         {
661             CPLError(CE_Failure, CPLE_AppDefined,
662                     "The generation of the product is in progress. Retry later");
663         }
664         else
665         {
666             CPLError(CE_Failure, CPLE_AppDefined,
667                      "%s", json_object_to_json_string_ext( poObj, JSON_C_TO_STRING_PRETTY ));
668             json_object_put(poObj);
669         }
670     }
671 
672     return poOutDS;
673 }
674 
675 /************************************************************************/
676 /*                                Open()                                */
677 /************************************************************************/
678 
Open(GDALOpenInfo * poOpenInfo)679 GDALDataset* OGRPLScenesDataV1Dataset::Open(GDALOpenInfo* poOpenInfo)
680 {
681     OGRPLScenesDataV1Dataset* poDS = new OGRPLScenesDataV1Dataset();
682 
683     poDS->m_osBaseURL = CPLGetConfigOption("PL_URL", "https://api.planet.com/data/v1/");
684 
685     char** papszOptions = CSLTokenizeStringComplex(
686             poOpenInfo->pszFilename+strlen("PLScenes:"), ",", TRUE, FALSE );
687 
688     poDS->m_osAPIKey = CSLFetchNameValueDef(papszOptions, "api_key",
689         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "API_KEY",
690                                 CPLGetConfigOption("PL_API_KEY","")) );
691     if( poDS->m_osAPIKey.empty() )
692     {
693         CPLError(CE_Failure, CPLE_AppDefined,
694                  "Missing PL_API_KEY configuration option or API_KEY open option");
695         delete poDS;
696         CSLDestroy(papszOptions);
697         return nullptr;
698     }
699 
700     poDS->m_bFollowLinks = CPLTestBool( CSLFetchNameValueDef(papszOptions, "follow_links",
701                 CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "FOLLOW_LINKS", "FALSE")) );
702 
703     poDS->m_osFilter = CSLFetchNameValueDef(papszOptions, "filter",
704                 CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "FILTER", ""));
705     poDS->m_osFilter.Trim();
706 
707     const char* pszScene = CSLFetchNameValueDef(papszOptions, "scene",
708                 CSLFetchNameValue(poOpenInfo->papszOpenOptions, "SCENE"));
709     if( pszScene )
710     {
711         GDALDataset* poRasterDS = poDS->OpenRasterScene(poOpenInfo, pszScene,
712                                                         papszOptions);
713         delete poDS;
714         CSLDestroy(papszOptions);
715         return poRasterDS;
716     }
717     else if( (poOpenInfo->nOpenFlags & GDAL_OF_RASTER) &&
718              !(poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) )
719     {
720         CPLError(CE_Failure, CPLE_AppDefined, "Missing scene");
721         delete poDS;
722         CSLDestroy(papszOptions);
723         return nullptr;
724     }
725 
726     for( char** papszIter = papszOptions; papszIter && *papszIter; papszIter ++ )
727     {
728         char* pszKey = nullptr;
729         const char* pszValue = CPLParseNameValue(*papszIter, &pszKey);
730         if( pszValue != nullptr )
731         {
732             if( !EQUAL(pszKey, "api_key") &&
733                 !EQUAL(pszKey, "version") &&
734                 !EQUAL(pszKey, "catalog") &&
735                 !EQUAL(pszKey, "itemtypes") &&
736                 !EQUAL(pszKey, "follow_links") &&
737                 !EQUAL(pszKey, "filter") )
738             {
739                 CPLError(CE_Failure, CPLE_NotSupported, "Unsupported option '%s'", pszKey);
740                 CPLFree(pszKey);
741                 delete poDS;
742                 CSLDestroy(papszOptions);
743                 return nullptr;
744             }
745             CPLFree(pszKey);
746         }
747     }
748 
749     json_object* poObj = poDS->RunRequest((poDS->m_osBaseURL + "item-types/").c_str());
750     if( poObj == nullptr )
751     {
752         delete poDS;
753         CSLDestroy(papszOptions);
754         return nullptr;
755     }
756 
757     const char* pszCatalog =
758         CSLFetchNameValueDef(papszOptions, "itemtypes",
759         CSLFetchNameValueDef(papszOptions, "catalog",
760         CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "ITEMTYPES",
761         CSLFetchNameValue(poOpenInfo->papszOpenOptions, "CATALOG"))));
762     if( pszCatalog == nullptr )
763     {
764         // Establish (partial if there are other pages) layer list.
765         if( !poDS->ParseItemTypes( poObj, poDS->m_osNextItemTypesPageURL) )
766         {
767             delete poDS;
768             poDS = nullptr;
769         }
770     }
771     else
772     {
773         if( poDS->GetLayerByName( pszCatalog ) == nullptr )
774         {
775             delete poDS;
776             poDS = nullptr;
777         }
778     }
779 
780     json_object_put(poObj);
781 
782     CSLDestroy(papszOptions);
783 
784     if( !(poOpenInfo->nOpenFlags & GDAL_OF_VECTOR) )
785     {
786         delete poDS;
787         return nullptr;
788     }
789 
790     return poDS;
791 }
792