1 /******************************************************************************
2  *
3  * Project:  PLMosaic driver
4  * Purpose:  PLMosaic driver
5  * Author:   Even Rouault, <even dot rouault at spatialys dot com>
6  *
7  ******************************************************************************
8  * Copyright (c) 2015-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 "cpl_minixml.h"
31 #include "gdal_frmts.h"
32 #include "gdal_pam.h"
33 #include "gdal_priv.h"
34 #include "ogr_spatialref.h"
35 #include "ogrsf_frmts.h"
36 #include "../vrt/gdal_vrt.h"
37 
38 #include "ogrgeojsonreader.h"
39 
40 #include <algorithm>
41 
42 CPL_CVSID("$Id: plmosaicdataset.cpp a5d5ed208537a05de4437e97b6a09b7ba44f76c9 2020-03-24 08:27:48 +0100 Kai Pastor $")
43 
44 #define SPHERICAL_RADIUS        6378137.0
45 #define GM_ORIGIN  -20037508.340
46 #define GM_ZOOM_0  ((2 * -(GM_ORIGIN)) / 256)
47 
48 /************************************************************************/
49 /* ==================================================================== */
50 /*                           PLMosaicDataset                            */
51 /* ==================================================================== */
52 /************************************************************************/
53 
54 class PLLinkedDataset;
55 class PLLinkedDataset
56 {
57 public:
58     CPLString            osKey;
59     GDALDataset         *poDS;
60     PLLinkedDataset       *psPrev;
61     PLLinkedDataset       *psNext;
62 
PLLinkedDataset()63                         PLLinkedDataset() : poDS(nullptr), psPrev(nullptr), psNext(nullptr) {}
64 };
65 
66 class PLMosaicRasterBand;
67 
68 class PLMosaicDataset final: public GDALPamDataset
69 {
70     friend class PLMosaicRasterBand;
71 
72         int                     bMustCleanPersistent;
73         CPLString               osCachePathRoot;
74         int                     bTrustCache;
75         CPLString               osBaseURL;
76         CPLString               osAPIKey;
77         CPLString               osMosaic;
78         char                   *pszWKT;
79         int                     nQuadSize;
80         CPLString               osQuadsURL;
81         int                     bHasGeoTransform;
82         double                  adfGeoTransform[6];
83         int                     nZoomLevelMax;
84         int                     bUseTMSForMain;
85         std::vector<GDALDataset*> apoTMSDS;
86         int                     nMetaTileXShift = 0;
87         int                     nMetaTileYShift = 0;
88         bool                    bQuadDownload = false;
89 
90         int                     nCacheMaxSize;
91         std::map<CPLString, PLLinkedDataset*> oMapLinkedDatasets;
92         PLLinkedDataset        *psHead;
93         PLLinkedDataset        *psTail;
94         void                    FlushDatasetsCache();
95         CPLString               GetMosaicCachePath();
96         void                    CreateMosaicCachePathIfNecessary();
97 
98         int                     nLastMetaTileX;
99         int                     nLastMetaTileY;
100         json_object            *poLastItemsInformation = nullptr;
101         CPLString               osLastRetGetLocationInfo;
102         const char             *GetLocationInfo(int nPixel, int nLine);
103 
104         char                  **GetBaseHTTPOptions();
105         CPLHTTPResult          *Download(const char* pszURL,
106                                          int bQuiet404Error = FALSE);
107         json_object            *RunRequest(const char* pszURL,
108                                            int bQuiet404Error = FALSE);
109         int                     OpenMosaic();
110         std::vector<CPLString>  ListSubdatasets();
111 
112         static CPLString        formatTileName(int tile_x, int tile_y);
113         void                    InsertNewDataset(CPLString osKey, GDALDataset* poDS);
114         GDALDataset*            OpenAndInsertNewDataset(CPLString osTmpFilename,
115                                                         CPLString osTilename);
116 
117   public:
118                 PLMosaicDataset();
119     virtual  ~PLMosaicDataset();
120 
121     static int Identify( GDALOpenInfo * poOpenInfo );
122     static GDALDataset  *Open( GDALOpenInfo * );
123 
124     virtual CPLErr  IRasterIO( GDALRWFlag eRWFlag,
125                                int nXOff, int nYOff, int nXSize, int nYSize,
126                                void * pData, int nBufXSize, int nBufYSize,
127                                GDALDataType eBufType,
128                                int nBandCount, int *panBandMap,
129                                GSpacing nPixelSpace, GSpacing nLineSpace,
130                                GSpacing nBandSpace,
131                                GDALRasterIOExtraArg* psExtraArg) override;
132 
133     virtual void FlushCache(void) override;
134 
135     virtual const char *_GetProjectionRef() override;
GetSpatialRef() const136     const OGRSpatialReference* GetSpatialRef() const override {
137         return GetSpatialRefFromOldGetProjectionRef();
138     }
139     virtual CPLErr      GetGeoTransform(double* padfGeoTransform) override;
140 
141     GDALDataset        *GetMetaTile(int tile_x, int tile_y);
142 };
143 
144 /************************************************************************/
145 /* ==================================================================== */
146 /*                         PLMosaicRasterBand                           */
147 /* ==================================================================== */
148 /************************************************************************/
149 
150 class PLMosaicRasterBand final: public GDALRasterBand
151 {
152     friend class PLMosaicDataset;
153 
154   public:
155 
156                 PLMosaicRasterBand( PLMosaicDataset * poDS, int nBand,
157                                     GDALDataType eDataType );
158 
159     virtual CPLErr          IReadBlock( int, int, void * ) override;
160     virtual CPLErr          IRasterIO( GDALRWFlag eRWFlag,
161                                   int nXOff, int nYOff, int nXSize, int nYSize,
162                                   void * pData, int nBufXSize, int nBufYSize,
163                                   GDALDataType eBufType,
164                                   GSpacing nPixelSpace, GSpacing nLineSpace,
165                                   GDALRasterIOExtraArg* psExtraArg) override;
166 
167     virtual const char     *GetMetadataItem( const char* pszName,
168                                              const char * pszDomain = "" ) override;
169 
170     virtual GDALColorInterp GetColorInterpretation() override;
171 
172     virtual int             GetOverviewCount() override;
173     virtual GDALRasterBand* GetOverview(int iOvrLevel) override;
174 };
175 
176 /************************************************************************/
177 /*                        PLMosaicRasterBand()                          */
178 /************************************************************************/
179 
PLMosaicRasterBand(PLMosaicDataset * poDSIn,int nBandIn,GDALDataType eDataTypeIn)180 PLMosaicRasterBand::PLMosaicRasterBand( PLMosaicDataset *poDSIn, int nBandIn,
181                                         GDALDataType eDataTypeIn )
182 
183 {
184     eDataType = eDataTypeIn;
185     nBlockXSize = 256;
186     nBlockYSize = 256;
187 
188     poDS = poDSIn;
189     nBand = nBandIn;
190 
191     if( eDataType == GDT_UInt16 )
192     {
193         if( nBand <= 3 )
194             SetMetadataItem("NBITS", "12", "IMAGE_STRUCTURE");
195     }
196 }
197 
198 /************************************************************************/
199 /*                             IReadBlock()                             */
200 /************************************************************************/
201 
IReadBlock(int nBlockXOff,int nBlockYOff,void * pImage)202 CPLErr PLMosaicRasterBand::IReadBlock( int nBlockXOff, int nBlockYOff,
203                                        void *pImage )
204 {
205     PLMosaicDataset* poMOSDS = reinterpret_cast<PLMosaicDataset *>( poDS );
206 
207 #ifdef DEBUG_VERBOSE
208     CPLDebug("PLMOSAIC", "IReadBlock(band=%d, x=%d, y=%d)",
209              nBand, nBlockYOff, nBlockYOff);
210 #endif
211 
212     if( poMOSDS->bUseTMSForMain && !poMOSDS->apoTMSDS.empty() )
213         return poMOSDS->apoTMSDS[0]->GetRasterBand(nBand)->ReadBlock(nBlockXOff, nBlockYOff,
214                                                                 pImage);
215 
216     const int bottom_yblock = (nRasterYSize - nBlockYOff * nBlockYSize) / nBlockYSize - 1;
217 
218     const int meta_tile_x = poMOSDS->nMetaTileXShift +
219                             (nBlockXOff * nBlockXSize) / poMOSDS->nQuadSize;
220     const int meta_tile_y = poMOSDS->nMetaTileYShift +
221                             (bottom_yblock * nBlockYSize) / poMOSDS->nQuadSize;
222     const int sub_tile_x = nBlockXOff % (poMOSDS->nQuadSize / nBlockXSize);
223     const int sub_tile_y = nBlockYOff % (poMOSDS->nQuadSize / nBlockYSize);
224 
225     GDALDataset *poMetaTileDS = poMOSDS->GetMetaTile(meta_tile_x, meta_tile_y);
226     if( poMetaTileDS == nullptr )
227     {
228         memset(pImage, 0,
229                nBlockXSize * nBlockYSize * (GDALGetDataTypeSize(eDataType)/8));
230         return CE_None;
231     }
232 
233     return poMetaTileDS->GetRasterBand(nBand)->
234                 RasterIO( GF_Read,
235                         sub_tile_x * nBlockXSize,
236                         sub_tile_y * nBlockYSize,
237                         nBlockXSize,
238                         nBlockYSize,
239                         pImage, nBlockXSize, nBlockYSize,
240                         eDataType, 0, 0, nullptr);
241 }
242 
243 /************************************************************************/
244 /*                             IRasterIO()                              */
245 /************************************************************************/
246 
IRasterIO(GDALRWFlag eRWFlag,int nXOff,int nYOff,int nXSize,int nYSize,void * pData,int nBufXSize,int nBufYSize,GDALDataType eBufType,GSpacing nPixelSpace,GSpacing nLineSpace,GDALRasterIOExtraArg * psExtraArg)247 CPLErr PLMosaicRasterBand::IRasterIO( GDALRWFlag eRWFlag,
248                                          int nXOff, int nYOff, int nXSize, int nYSize,
249                                          void * pData, int nBufXSize, int nBufYSize,
250                                          GDALDataType eBufType,
251                                          GSpacing nPixelSpace, GSpacing nLineSpace,
252                                          GDALRasterIOExtraArg* psExtraArg )
253 {
254     PLMosaicDataset* poMOSDS = reinterpret_cast<PLMosaicDataset *>( poDS );
255     if( poMOSDS->bUseTMSForMain && !poMOSDS->apoTMSDS.empty() )
256         return poMOSDS->apoTMSDS[0]->GetRasterBand(nBand)->RasterIO(
257                                          eRWFlag, nXOff, nYOff, nXSize, nYSize,
258                                          pData, nBufXSize, nBufYSize, eBufType,
259                                          nPixelSpace, nLineSpace, psExtraArg );
260 
261     return GDALRasterBand::IRasterIO( eRWFlag, nXOff, nYOff, nXSize, nYSize,
262                                          pData, nBufXSize, nBufYSize, eBufType,
263                                          nPixelSpace, nLineSpace, psExtraArg );
264 }
265 
266 /************************************************************************/
267 /*                         GetMetadataItem()                            */
268 /************************************************************************/
269 
GetMetadataItem(const char * pszName,const char * pszDomain)270 const char* PLMosaicRasterBand::GetMetadataItem( const char* pszName,
271                                                  const char* pszDomain )
272 {
273     PLMosaicDataset* poMOSDS = reinterpret_cast<PLMosaicDataset *>( poDS );
274     int nPixel, nLine;
275     if( poMOSDS->bQuadDownload &&
276         pszName != nullptr && pszDomain != nullptr &&
277         EQUAL(pszDomain, "LocationInfo") &&
278         sscanf(pszName, "Pixel_%d_%d", &nPixel, &nLine) == 2 )
279     {
280         return poMOSDS->GetLocationInfo(nPixel, nLine);
281     }
282 
283     return GDALRasterBand::GetMetadataItem(pszName, pszDomain);
284 }
285 
286 /************************************************************************/
287 /*                         GetOverviewCount()                           */
288 /************************************************************************/
289 
GetOverviewCount()290 int PLMosaicRasterBand::GetOverviewCount()
291 {
292     PLMosaicDataset *poGDS = reinterpret_cast<PLMosaicDataset *>( poDS );
293     return std::max(0, static_cast<int>(poGDS->apoTMSDS.size()) - 1);
294 }
295 
296 /************************************************************************/
297 /*                            GetOverview()                             */
298 /************************************************************************/
299 
GetOverview(int iOvrLevel)300 GDALRasterBand* PLMosaicRasterBand::GetOverview(int iOvrLevel)
301 {
302     PLMosaicDataset *poGDS = reinterpret_cast<PLMosaicDataset *>( poDS );
303     if (iOvrLevel < 0 ||
304         iOvrLevel >= static_cast<int>(poGDS->apoTMSDS.size()) - 1)
305         return nullptr;
306 
307     poGDS->CreateMosaicCachePathIfNecessary();
308 
309     return poGDS->apoTMSDS[iOvrLevel+1]->GetRasterBand(nBand);
310 }
311 
312 /************************************************************************/
313 /*                       GetColorInterpretation()                       */
314 /************************************************************************/
315 
GetColorInterpretation()316 GDALColorInterp PLMosaicRasterBand::GetColorInterpretation()
317 {
318     switch( nBand )
319     {
320         case 1:
321             return GCI_RedBand;
322         case 2:
323             return GCI_GreenBand;
324         case 3:
325             return GCI_BlueBand;
326         case 4:
327             return GCI_AlphaBand;
328         default:
329             CPLAssert(false);
330             return GCI_GrayIndex;
331     }
332 }
333 
334 /************************************************************************/
335 /* ==================================================================== */
336 /*                           PLMosaicDataset                            */
337 /* ==================================================================== */
338 /************************************************************************/
339 
340 /************************************************************************/
341 /*                        PLMosaicDataset()                            */
342 /************************************************************************/
343 
PLMosaicDataset()344 PLMosaicDataset::PLMosaicDataset() :
345     bMustCleanPersistent(FALSE),
346     bTrustCache(FALSE),
347     pszWKT(nullptr),
348     nQuadSize(0),
349     bHasGeoTransform(FALSE),
350     nZoomLevelMax(0),
351     bUseTMSForMain(FALSE),
352     nCacheMaxSize(10),
353     psHead(nullptr),
354     psTail(nullptr),
355     nLastMetaTileX(-1),
356     nLastMetaTileY(-1)
357 {
358     adfGeoTransform[0] = 0;
359     adfGeoTransform[1] = 1;
360     adfGeoTransform[2] = 0;
361     adfGeoTransform[3] = 0;
362     adfGeoTransform[4] = 0;
363     adfGeoTransform[5] = 1;
364 
365     SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
366     osCachePathRoot = CPLGetPath(CPLGenerateTempFilename(""));
367 }
368 
369 /************************************************************************/
370 /*                         ~PLMosaicDataset()                           */
371 /************************************************************************/
372 
~PLMosaicDataset()373 PLMosaicDataset::~PLMosaicDataset()
374 
375 {
376     PLMosaicDataset::FlushCache();
377     CPLFree(pszWKT);
378     for( auto& poDS: apoTMSDS )
379         delete poDS;
380     if( poLastItemsInformation )
381         json_object_put(poLastItemsInformation);
382     if (bMustCleanPersistent)
383     {
384         char** papszOptions
385             = CSLSetNameValue(nullptr, "CLOSE_PERSISTENT",
386                               CPLSPrintf("PLMOSAIC:%p", this));
387         CPLHTTPDestroyResult( CPLHTTPFetch( osBaseURL, papszOptions) );
388         CSLDestroy(papszOptions);
389     }
390 }
391 
392 /************************************************************************/
393 /*                      FlushDatasetsCache()                            */
394 /************************************************************************/
395 
FlushDatasetsCache()396 void PLMosaicDataset::FlushDatasetsCache()
397 {
398     for( PLLinkedDataset* psIter = psHead; psIter != nullptr;  )
399     {
400         PLLinkedDataset* psNext = psIter->psNext;
401         if( psIter->poDS )
402             GDALClose(psIter->poDS);
403         delete psIter;
404         psIter = psNext;
405     }
406     psHead = nullptr;
407     psTail = nullptr;
408     oMapLinkedDatasets.clear();
409 }
410 
411 /************************************************************************/
412 /*                            FlushCache()                              */
413 /************************************************************************/
414 
FlushCache()415 void PLMosaicDataset::FlushCache()
416 {
417     FlushDatasetsCache();
418 
419     nLastMetaTileX = -1;
420     nLastMetaTileY = -1;
421     if( poLastItemsInformation )
422         json_object_put(poLastItemsInformation);
423     poLastItemsInformation = nullptr;
424     osLastRetGetLocationInfo.clear();
425 
426     GDALDataset::FlushCache();
427 }
428 
429 /************************************************************************/
430 /*                            Identify()                                */
431 /************************************************************************/
432 
Identify(GDALOpenInfo * poOpenInfo)433 int PLMosaicDataset::Identify( GDALOpenInfo * poOpenInfo )
434 
435 {
436     return STARTS_WITH_CI(poOpenInfo->pszFilename, "PLMOSAIC:");
437 }
438 
439 /************************************************************************/
440 /*                          GetBaseHTTPOptions()                         */
441 /************************************************************************/
442 
GetBaseHTTPOptions()443 char** PLMosaicDataset::GetBaseHTTPOptions()
444 {
445     bMustCleanPersistent = TRUE;
446 
447     char** papszOptions
448         = CSLAddString(nullptr, CPLSPrintf("PERSISTENT=PLMOSAIC:%p", this));
449     /* Use basic auth, rather than Authorization headers since curl would forward it to S3 */
450     papszOptions = CSLAddString(papszOptions, CPLSPrintf("USERPWD=%s:", osAPIKey.c_str()));
451 
452     return papszOptions;
453 }
454 
455 /************************************************************************/
456 /*                               Download()                             */
457 /************************************************************************/
458 
Download(const char * pszURL,int bQuiet404Error)459 CPLHTTPResult* PLMosaicDataset::Download(const char* pszURL,
460                                          int bQuiet404Error)
461 {
462     char** papszOptions = CSLAddString(GetBaseHTTPOptions(), nullptr);
463     CPLHTTPResult *psResult = nullptr;
464     if( STARTS_WITH(osBaseURL, "/vsimem/") &&
465         STARTS_WITH(pszURL, "/vsimem/") )
466     {
467         CPLDebug("PLSCENES", "Fetching %s", pszURL);
468         psResult = reinterpret_cast<CPLHTTPResult *>(
469             CPLCalloc( 1, sizeof( CPLHTTPResult ) ) );
470         vsi_l_offset nDataLength = 0;
471         CPLString osURL(pszURL);
472         if( osURL.back() == '/' )
473             osURL.resize(osURL.size()-1);
474         GByte* pabyBuf = VSIGetMemFileBuffer(osURL, &nDataLength, FALSE);
475         if( pabyBuf )
476         {
477             psResult->pabyData = reinterpret_cast<GByte *>(
478                 VSIMalloc(1 + static_cast<size_t>( nDataLength ) ) );
479             if( psResult->pabyData )
480             {
481                 memcpy(psResult->pabyData, pabyBuf, static_cast<size_t>( nDataLength ) );
482                 psResult->pabyData[nDataLength] = 0;
483                 psResult->nDataLen = static_cast<int>( nDataLength );
484             }
485         }
486         else
487         {
488             psResult->pszErrBuf =
489                 CPLStrdup(CPLSPrintf("Error 404. Cannot find %s", pszURL));
490         }
491     }
492     else
493     {
494         if( bQuiet404Error )
495             CPLPushErrorHandler(CPLQuietErrorHandler);
496         psResult = CPLHTTPFetch( pszURL, papszOptions);
497         if( bQuiet404Error )
498             CPLPopErrorHandler();
499     }
500     CSLDestroy(papszOptions);
501 
502     if( psResult->pszErrBuf != nullptr )
503     {
504         if( !(bQuiet404Error && strstr(psResult->pszErrBuf, "404")) )
505         {
506             CPLError( CE_Failure, CPLE_AppDefined, "%s",
507                       psResult->pabyData ? reinterpret_cast<const char*>(
508                           psResult->pabyData ) :
509                       psResult->pszErrBuf );
510         }
511         CPLHTTPDestroyResult(psResult);
512         return nullptr;
513     }
514 
515     if( psResult->pabyData == nullptr )
516     {
517         CPLError(CE_Failure, CPLE_AppDefined, "Empty content returned by server");
518         CPLHTTPDestroyResult(psResult);
519         return nullptr;
520     }
521 
522     return psResult;
523 }
524 
525 /************************************************************************/
526 /*                               RunRequest()                           */
527 /************************************************************************/
528 
RunRequest(const char * pszURL,int bQuiet404Error)529 json_object* PLMosaicDataset::RunRequest(const char* pszURL,
530                                          int bQuiet404Error)
531 {
532     CPLHTTPResult * psResult = Download(pszURL, bQuiet404Error);
533     if( psResult == nullptr )
534     {
535         return nullptr;
536     }
537 
538     json_object* poObj = nullptr;
539     const char* pszText = reinterpret_cast<const char*>(psResult->pabyData);
540     if( !OGRJSonParse(pszText, &poObj, true) )
541     {
542         CPLHTTPDestroyResult(psResult);
543         return nullptr;
544     }
545 
546     CPLHTTPDestroyResult(psResult);
547 
548     if( json_object_get_type(poObj) != json_type_object )
549     {
550         CPLError( CE_Failure, CPLE_AppDefined, "Return is not a JSON dictionary");
551         json_object_put(poObj);
552         poObj = nullptr;
553     }
554 
555     return poObj;
556 }
557 
558 /************************************************************************/
559 /*                           PLMosaicGetParameter()                     */
560 /************************************************************************/
561 
PLMosaicGetParameter(GDALOpenInfo * poOpenInfo,char ** papszOptions,const char * pszName,const char * pszDefaultVal)562 static CPLString PLMosaicGetParameter( GDALOpenInfo * poOpenInfo,
563                                        char** papszOptions,
564                                        const char* pszName,
565                                        const char* pszDefaultVal )
566 {
567     return CSLFetchNameValueDef( papszOptions, pszName,
568         CSLFetchNameValueDef( poOpenInfo->papszOpenOptions, pszName,
569                               pszDefaultVal ));
570 }
571 
572 /************************************************************************/
573 /*                                Open()                                */
574 /************************************************************************/
575 
Open(GDALOpenInfo * poOpenInfo)576 GDALDataset *PLMosaicDataset::Open( GDALOpenInfo * poOpenInfo )
577 
578 {
579     if (!Identify(poOpenInfo) )
580         return nullptr;
581 
582     PLMosaicDataset* poDS = new PLMosaicDataset();
583 
584     poDS->osBaseURL = CPLGetConfigOption("PL_URL", "https://api.planet.com/basemaps/v1/mosaics");
585 
586     char** papszOptions = CSLTokenizeStringComplex(
587             poOpenInfo->pszFilename+strlen("PLMosaic:"), ",", TRUE, FALSE );
588     for( char** papszIter = papszOptions; papszIter && *papszIter; papszIter ++ )
589     {
590         char* pszKey = nullptr;
591         const char* pszValue = CPLParseNameValue(*papszIter, &pszKey);
592         if( pszValue != nullptr )
593         {
594             if( !EQUAL(pszKey, "api_key") &&
595                 !EQUAL(pszKey, "mosaic") &&
596                 !EQUAL(pszKey, "cache_path") &&
597                 !EQUAL(pszKey, "trust_cache") &&
598                 !EQUAL(pszKey, "use_tiles") )
599             {
600                 CPLError(CE_Failure, CPLE_NotSupported, "Unsupported option %s", pszKey);
601                 CPLFree(pszKey);
602                 delete poDS;
603                 CSLDestroy(papszOptions);
604                 return nullptr;
605             }
606             CPLFree(pszKey);
607         }
608     }
609 
610     poDS->osAPIKey = PLMosaicGetParameter(poOpenInfo, papszOptions, "api_key",
611                                           CPLGetConfigOption("PL_API_KEY",""));
612 
613     if( poDS->osAPIKey.empty() )
614     {
615         CPLError(CE_Failure, CPLE_AppDefined,
616                  "Missing PL_API_KEY configuration option or API_KEY open option");
617         delete poDS;
618         CSLDestroy(papszOptions);
619         return nullptr;
620     }
621 
622     poDS->osMosaic = PLMosaicGetParameter(poOpenInfo, papszOptions, "mosaic", "");
623 
624     poDS->osCachePathRoot = PLMosaicGetParameter(poOpenInfo, papszOptions, "cache_path",
625                                           CPLGetConfigOption("PL_CACHE_PATH",""));
626 
627     poDS->bTrustCache = CPLTestBool(PLMosaicGetParameter(
628                         poOpenInfo, papszOptions, "trust_cache", "FALSE"));
629 
630     poDS->bUseTMSForMain = CPLTestBool(PLMosaicGetParameter(
631                         poOpenInfo, papszOptions, "use_tiles", "FALSE"));
632 
633     CSLDestroy(papszOptions);
634     papszOptions = nullptr;
635 
636     if( !poDS->osMosaic.empty() )
637     {
638         if( !poDS->OpenMosaic() )
639         {
640             delete poDS;
641             poDS = nullptr;
642         }
643     }
644     else
645     {
646         auto aosNameList = poDS->ListSubdatasets();
647         if( aosNameList.empty() )
648         {
649             delete poDS;
650             poDS = nullptr;
651         }
652         else if( aosNameList.size() == 1 )
653         {
654             const CPLString osOldFilename(poOpenInfo->pszFilename);
655             const CPLString osMosaicConnectionString
656                 = CPLSPrintf("PLMOSAIC:mosaic=%s", aosNameList[0].c_str());
657             delete poDS;
658             GDALOpenInfo oOpenInfo(osMosaicConnectionString.c_str(), GA_ReadOnly);
659             oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
660             poDS = reinterpret_cast<PLMosaicDataset *>( Open(&oOpenInfo) );
661             if( poDS )
662                 poDS->SetDescription(osOldFilename);
663         }
664         else
665         {
666             CPLStringList aosSubdatasets;
667             for( const auto& osName: aosNameList )
668             {
669                 const int nDatasetIdx = aosSubdatasets.Count() / 2 + 1;
670                 aosSubdatasets.AddNameValue(
671                     CPLSPrintf("SUBDATASET_%d_NAME", nDatasetIdx),
672                     CPLSPrintf("PLMOSAIC:mosaic=%s", osName.c_str()));
673                 aosSubdatasets.AddNameValue(
674                     CPLSPrintf("SUBDATASET_%d_DESC", nDatasetIdx),
675                     CPLSPrintf("Mosaic %s", osName.c_str()));
676             }
677             poDS->SetMetadata(aosSubdatasets.List(), "SUBDATASETS");
678         }
679     }
680 
681     if( poDS )
682         poDS->SetPamFlags(0);
683 
684     return poDS;
685 }
686 
687 /************************************************************************/
688 /*                           ReplaceSubString()                         */
689 /************************************************************************/
690 
ReplaceSubString(CPLString & osTarget,CPLString osPattern,CPLString osReplacement)691 static void ReplaceSubString(CPLString &osTarget,
692                              CPLString osPattern,
693                              CPLString osReplacement)
694 
695 {
696     // Assumes only one occurrence of osPattern.
697     size_t pos = osTarget.find(osPattern);
698     if( pos == CPLString::npos )
699         return;
700 
701     osTarget.replace(pos, osPattern.size(), osReplacement);
702 }
703 
704 /************************************************************************/
705 /*                            GetMosaicCachePath()                      */
706 /************************************************************************/
707 
GetMosaicCachePath()708 CPLString PLMosaicDataset::GetMosaicCachePath()
709 {
710     if( !osCachePathRoot.empty() )
711     {
712         const CPLString osCachePath(
713             CPLFormFilename(osCachePathRoot, "plmosaic_cache", nullptr));
714         const CPLString osMosaicPath(
715             CPLFormFilename(osCachePath, osMosaic, nullptr));
716 
717         return osMosaicPath;
718     }
719     return "";
720 }
721 
722 /************************************************************************/
723 /*                     CreateMosaicCachePathIfNecessary()               */
724 /************************************************************************/
725 
CreateMosaicCachePathIfNecessary()726 void PLMosaicDataset::CreateMosaicCachePathIfNecessary()
727 {
728     if( !osCachePathRoot.empty() )
729     {
730         const CPLString osCachePath(
731             CPLFormFilename(osCachePathRoot, "plmosaic_cache", nullptr));
732         const CPLString osMosaicPath(
733             CPLFormFilename(osCachePath, osMosaic, nullptr));
734 
735         VSIStatBufL sStatBuf;
736         if( VSIStatL(osMosaicPath, &sStatBuf) != 0 )
737         {
738             CPLPushErrorHandler(CPLQuietErrorHandler);
739             VSIMkdir(osCachePathRoot, 0755);
740             VSIMkdir(osCachePath, 0755);
741             VSIMkdir(osMosaicPath, 0755);
742             CPLPopErrorHandler();
743         }
744     }
745 }
746 
747 /************************************************************************/
748 /*                     LongLatToSphericalMercator()                     */
749 /************************************************************************/
750 
LongLatToSphericalMercator(double * x,double * y)751 static void LongLatToSphericalMercator(double* x, double* y)
752 {
753   double X = SPHERICAL_RADIUS * (*x) / 180 * M_PI;
754   double Y = SPHERICAL_RADIUS * log( tan(M_PI / 4 + 0.5 * (*y) / 180 * M_PI) );
755   *x = X;
756   *y = Y;
757 }
758 
759 /************************************************************************/
760 /*                               OpenMosaic()                           */
761 /************************************************************************/
762 
OpenMosaic()763 int PLMosaicDataset::OpenMosaic()
764 {
765     CPLString osURL;
766 
767     osURL = osBaseURL;
768     if( osURL.back() != '/' )
769         osURL += '/';
770     char* pszEscaped = CPLEscapeString(osMosaic, -1, CPLES_URL);
771     osURL += "?name__is=" + CPLString(pszEscaped);
772     CPLFree(pszEscaped);
773 
774     json_object* poObj = RunRequest(osURL);
775     if( poObj == nullptr )
776     {
777         return FALSE;
778     }
779 
780     json_object* poMosaics = CPL_json_object_object_get(poObj, "mosaics");
781     json_object* poMosaic = nullptr;
782     if( poMosaics == nullptr ||
783         json_object_get_type(poMosaics) != json_type_array ||
784         json_object_array_length(poMosaics) != 1 ||
785         (poMosaic = json_object_array_get_idx(poMosaics, 0)) == nullptr ||
786         json_object_get_type(poMosaic) != json_type_object )
787     {
788         CPLError(CE_Failure, CPLE_AppDefined,
789                  "No mosaic %s", osMosaic.c_str());
790         json_object_put(poObj);
791         return FALSE;
792     }
793 
794     json_object* poId = CPL_json_object_object_get(poMosaic, "id");
795     json_object* poCoordinateSystem = CPL_json_object_object_get(poMosaic, "coordinate_system");
796     json_object* poDataType = CPL_json_object_object_get(poMosaic, "datatype");
797     json_object* poQuadSize = json_ex_get_object_by_path(poMosaic, "grid.quad_size");
798     json_object* poResolution = json_ex_get_object_by_path(poMosaic, "grid.resolution");
799     json_object* poLinks = CPL_json_object_object_get(poMosaic, "_links");
800     json_object* poLinksTiles = nullptr;
801     json_object* poBBox = CPL_json_object_object_get(poMosaic, "bbox");
802     if( poLinks != nullptr && json_object_get_type(poLinks) == json_type_object )
803     {
804         poLinksTiles = CPL_json_object_object_get(poLinks, "tiles");
805     }
806     if( poId == nullptr || json_object_get_type(poId) != json_type_string ||
807         poCoordinateSystem == nullptr || json_object_get_type(poCoordinateSystem) != json_type_string ||
808         poDataType == nullptr || json_object_get_type(poDataType) != json_type_string ||
809         poQuadSize == nullptr || json_object_get_type(poQuadSize) != json_type_int ||
810         poResolution == nullptr || (json_object_get_type(poResolution) != json_type_int &&
811                                  json_object_get_type(poResolution) != json_type_double) )
812     {
813         CPLError(CE_Failure, CPLE_NotSupported, "Missing required parameter");
814         json_object_put(poObj);
815         return FALSE;
816     }
817 
818     CPLString osId(json_object_get_string(poId));
819 
820     const char* pszSRS = json_object_get_string(poCoordinateSystem);
821     if( !EQUAL(pszSRS, "EPSG:3857") )
822     {
823         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported coordinate_system = %s",
824                  pszSRS);
825         json_object_put(poObj);
826         return FALSE;
827     }
828 
829     OGRSpatialReference oSRS;
830     oSRS.SetFromUserInput(pszSRS);
831     oSRS.exportToWkt(&pszWKT);
832 
833     json_object* poQuadDownload = CPL_json_object_object_get(
834                                         poMosaic, "quad_download");
835     bQuadDownload = CPL_TO_BOOL(json_object_get_boolean(poQuadDownload));
836 
837     GDALDataType eDT = GDT_Unknown;
838     const char* pszDataType = json_object_get_string(poDataType);
839     if( EQUAL(pszDataType, "byte") )
840         eDT = GDT_Byte;
841     else if( EQUAL(pszDataType, "uint16") )
842         eDT = GDT_UInt16;
843     else if( EQUAL(pszDataType, "int16") )
844         eDT = GDT_Int16;
845     else
846     {
847         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported data_type = %s",
848                  pszDataType);
849         json_object_put(poObj);
850         return FALSE;
851     }
852 
853     if( eDT == GDT_Byte && !bQuadDownload )
854         bUseTMSForMain = true;
855 
856     if( bUseTMSForMain && eDT != GDT_Byte )
857     {
858         CPLError(CE_Failure, CPLE_NotSupported,
859                  "Cannot use tile API for full resolution data on non Byte mosaic");
860         bUseTMSForMain = FALSE;
861     }
862 
863     nQuadSize = json_object_get_int(poQuadSize);
864     if( nQuadSize <= 0 || (nQuadSize % 256) != 0 )
865     {
866         CPLError(CE_Failure, CPLE_NotSupported, "Unsupported quad_size = %d",
867                  nQuadSize);
868         json_object_put(poObj);
869         return FALSE;
870     }
871 
872     const double dfResolution = json_object_get_double(poResolution);
873     if( EQUAL(pszSRS, "EPSG:3857") )
874     {
875         double dfZoomLevel = log(GM_ZOOM_0 / dfResolution)/log(2.0);
876         nZoomLevelMax = static_cast<int>( dfZoomLevel + 0.1 );
877         if( fabs(dfZoomLevel - nZoomLevelMax) > 1e-5 )
878         {
879             CPLError(CE_Failure, CPLE_NotSupported, "Unsupported resolution = %.12g",
880                     dfResolution);
881             json_object_put(poObj);
882             return FALSE;
883         }
884 
885         bHasGeoTransform = TRUE;
886         adfGeoTransform[0] = GM_ORIGIN;
887         adfGeoTransform[1] = dfResolution;
888         adfGeoTransform[2] = 0;
889         adfGeoTransform[3] = -GM_ORIGIN;
890         adfGeoTransform[4] = 0;
891         adfGeoTransform[5] = -dfResolution;
892         nRasterXSize = static_cast<int>( 2 * -GM_ORIGIN / dfResolution + 0.5 );
893         nRasterYSize = nRasterXSize;
894 
895         if( poBBox != nullptr &&
896             json_object_get_type(poBBox) == json_type_array &&
897             json_object_array_length(poBBox) == 4 )
898         {
899             double xmin =
900                 json_object_get_double(json_object_array_get_idx(poBBox, 0));
901             double ymin =
902                 json_object_get_double(json_object_array_get_idx(poBBox, 1));
903             double xmax =
904                 json_object_get_double(json_object_array_get_idx(poBBox, 2));
905             double ymax =
906                 json_object_get_double(json_object_array_get_idx(poBBox, 3));
907             LongLatToSphericalMercator(&xmin, &ymin);
908             LongLatToSphericalMercator(&xmax, &ymax);
909             xmin = std::max(xmin, GM_ORIGIN);
910             ymin = std::max(ymin, GM_ORIGIN);
911             xmax = std::min(xmax, -GM_ORIGIN);
912             ymax = std::min(ymax, -GM_ORIGIN);
913 
914             double dfTileSize = dfResolution * nQuadSize;
915             xmin = floor(xmin / dfTileSize) * dfTileSize;
916             ymin = floor(ymin / dfTileSize) * dfTileSize;
917             xmax = ceil(xmax / dfTileSize) * dfTileSize;
918             ymax = ceil(ymax / dfTileSize) * dfTileSize;
919             adfGeoTransform[0] = xmin;
920             adfGeoTransform[3] = ymax;
921             nRasterXSize = static_cast<int>((xmax - xmin) / dfResolution + 0.5);
922             nRasterYSize = static_cast<int>((ymax - ymin) / dfResolution + 0.5);
923             nMetaTileXShift = static_cast<int>((xmin - GM_ORIGIN) / dfTileSize + 0.5);
924             nMetaTileYShift = static_cast<int>((ymin - GM_ORIGIN) / dfTileSize + 0.5);
925         }
926     }
927 
928     osQuadsURL = osBaseURL;
929     if( osQuadsURL.back() != '/' )
930         osQuadsURL += '/';
931     osQuadsURL += osId + "/quads/";
932 
933     // Use WMS/TMS driver for overviews (only for byte)
934     if( eDT == GDT_Byte && EQUAL(pszSRS, "EPSG:3857") &&
935         poLinksTiles != nullptr &&
936         json_object_get_type(poLinksTiles) == json_type_string )
937     {
938         const char* pszLinksTiles = json_object_get_string(poLinksTiles);
939         if( strstr(pszLinksTiles, "{x}") == nullptr ||
940             strstr(pszLinksTiles, "{y}") == nullptr ||
941             strstr(pszLinksTiles, "{z}") == nullptr )
942         {
943             CPLError(CE_Warning, CPLE_NotSupported, "Invalid _links.tiles = %s",
944                      pszLinksTiles);
945         }
946         else
947         {
948             CPLString osCacheStr;
949             if( !osCachePathRoot.empty() )
950             {
951                 osCacheStr = "    <Cache><Path>";
952                 osCacheStr += GetMosaicCachePath();
953                 osCacheStr += "</Path><Unique>False</Unique></Cache>\n";
954             }
955 
956             CPLString osTMSURL(pszLinksTiles);
957             ReplaceSubString(osTMSURL, "{x}", "${x}");
958             ReplaceSubString(osTMSURL, "{y}", "${y}");
959             ReplaceSubString(osTMSURL, "{z}", "${z}");
960             ReplaceSubString(osTMSURL, "{0-3}", "0");
961 
962             for( int nZoomLevel = nZoomLevelMax; nZoomLevel >= 0;
963                      nZoomLevel -- )
964             {
965                 const int nZShift = nZoomLevelMax - nZoomLevel;
966                 int nOvrXSize = nRasterXSize >> nZShift;
967                 int nOvrYSize = nRasterYSize >> nZShift;
968                 if( nOvrXSize == 0 || nOvrYSize == 0 )
969                     break;
970 
971                 CPLString osTMS = CPLSPrintf(
972     "<GDAL_WMS>\n"
973     "    <Service name=\"TMS\">\n"
974     "        <ServerUrl>%s</ServerUrl>\n"
975     "    </Service>\n"
976     "    <DataWindow>\n"
977     "        <UpperLeftX>%.16g</UpperLeftX>\n"
978     "        <UpperLeftY>%.16g</UpperLeftY>\n"
979     "        <LowerRightX>%.16g</LowerRightX>\n"
980     "        <LowerRightY>%.16g</LowerRightY>\n"
981     "        <SizeX>%d</SizeX>\n"
982     "        <SizeY>%d</SizeY>\n"
983     "        <TileLevel>%d</TileLevel>\n"
984     "        <YOrigin>top</YOrigin>\n"
985     "    </DataWindow>\n"
986     "    <Projection>%s</Projection>\n"
987     "    <BlockSizeX>256</BlockSizeX>\n"
988     "    <BlockSizeY>256</BlockSizeY>\n"
989     "    <BandsCount>4</BandsCount>\n"
990     "%s"
991     "</GDAL_WMS>",
992                     osTMSURL.c_str(),
993                     GM_ORIGIN,
994                     -GM_ORIGIN,
995                     -GM_ORIGIN,
996                     GM_ORIGIN,
997                     256 << nZoomLevel,
998                     256 << nZoomLevel,
999                     nZoomLevel,
1000                     pszSRS,
1001                     osCacheStr.c_str());
1002 
1003                 GDALDataset* poTMSDS = reinterpret_cast<GDALDataset *>(
1004                         GDALOpenEx( osTMS, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
1005                                 nullptr, nullptr, nullptr ) );
1006                 if( poTMSDS )
1007                 {
1008                     double dfThisResolution = dfResolution * (1 << nZShift);
1009 
1010                     VRTDatasetH hVRTDS = VRTCreate(nOvrXSize, nOvrYSize);
1011                     for(int iBand=1;iBand<=4;iBand++)
1012                     {
1013                         VRTAddBand( hVRTDS, GDT_Byte, nullptr );
1014                     }
1015 
1016                     int nSrcXOff, nSrcYOff, nDstXOff, nDstYOff;
1017 
1018                     nSrcXOff = static_cast<int>(0.5 +
1019                         (adfGeoTransform[0] - GM_ORIGIN) / dfThisResolution);
1020                     nDstXOff = 0;
1021 
1022                     nSrcYOff = static_cast<int>(0.5 +
1023                         (-GM_ORIGIN - adfGeoTransform[3]) / dfThisResolution);
1024                     nDstYOff = 0;
1025 
1026                     for(int iBand=1;iBand<=4;iBand++)
1027                     {
1028                         VRTSourcedRasterBandH hVRTBand =
1029                             reinterpret_cast<VRTSourcedRasterBandH>(
1030                                 GDALGetRasterBand(hVRTDS, iBand));
1031                         VRTAddSimpleSource(
1032                             hVRTBand, GDALGetRasterBand(poTMSDS, iBand),
1033                             nSrcXOff, nSrcYOff, nOvrXSize, nOvrYSize,
1034                             nDstXOff, nDstYOff, nOvrXSize, nOvrYSize,
1035                             "NEAR", VRT_NODATA_UNSET);
1036                     }
1037                     poTMSDS->Dereference();
1038 
1039                     apoTMSDS.push_back( reinterpret_cast<GDALDataset*>(hVRTDS) );
1040                 }
1041 
1042                 if( nOvrXSize < 256 && nOvrYSize < 256 )
1043                     break;
1044             }
1045         }
1046     }
1047 
1048     if( bUseTMSForMain && apoTMSDS.empty() )
1049     {
1050         CPLError(CE_Failure, CPLE_NotSupported,
1051                  "Cannot find tile definition, so use_tiles will be ignored");
1052         bUseTMSForMain = FALSE;
1053     }
1054 
1055     for(int i=0;i<4;i++)
1056         SetBand(i + 1, new PLMosaicRasterBand(this, i + 1, eDT));
1057 
1058     json_object* poFirstAcquired = CPL_json_object_object_get(poMosaic, "first_acquired");
1059     if( poFirstAcquired != nullptr && json_object_get_type(poFirstAcquired) == json_type_string )
1060     {
1061         SetMetadataItem("FIRST_ACQUIRED",
1062                                  json_object_get_string(poFirstAcquired));
1063     }
1064     json_object* poLastAcquired = CPL_json_object_object_get(poMosaic, "last_acquired");
1065     if( poLastAcquired != nullptr && json_object_get_type(poLastAcquired) == json_type_string )
1066     {
1067         SetMetadataItem("LAST_ACQUIRED",
1068                                  json_object_get_string(poLastAcquired));
1069     }
1070     json_object* poName = CPL_json_object_object_get(poMosaic, "name");
1071     if( poName != nullptr && json_object_get_type(poName) == json_type_string )
1072     {
1073         SetMetadataItem("NAME", json_object_get_string(poName));
1074     }
1075 
1076     json_object_put(poObj);
1077     return TRUE;
1078 }
1079 
1080 /************************************************************************/
1081 /*                          ListSubdatasets()                           */
1082 /************************************************************************/
1083 
ListSubdatasets()1084 std::vector<CPLString> PLMosaicDataset::ListSubdatasets()
1085 {
1086     std::vector<CPLString> aosNameList;
1087     CPLString osURL(osBaseURL);
1088     while(osURL.size())
1089     {
1090         json_object* poObj = RunRequest(osURL);
1091         if( poObj == nullptr )
1092         {
1093             return aosNameList;
1094         }
1095 
1096         osURL = "";
1097         json_object* poLinks = CPL_json_object_object_get(poObj, "_links");
1098         if( poLinks != nullptr && json_object_get_type(poLinks) == json_type_object )
1099         {
1100             json_object* poNext = CPL_json_object_object_get(poLinks, "_next");
1101             if( poNext != nullptr && json_object_get_type(poNext) == json_type_string )
1102             {
1103                 osURL = json_object_get_string(poNext);
1104             }
1105         }
1106 
1107         json_object* poMosaics = CPL_json_object_object_get(poObj, "mosaics");
1108         if( poMosaics == nullptr || json_object_get_type(poMosaics) != json_type_array )
1109         {
1110             json_object_put(poObj);
1111             return aosNameList;
1112         }
1113 
1114         const auto nMosaics = json_object_array_length(poMosaics);
1115         for(auto i=decltype(nMosaics){0};i< nMosaics;i++)
1116         {
1117             const char* pszName = nullptr;
1118             const char* pszCoordinateSystem = nullptr;
1119             json_object* poMosaic = json_object_array_get_idx(poMosaics, i);
1120             bool bAccessible = false;
1121             if( poMosaic && json_object_get_type(poMosaic) == json_type_object )
1122             {
1123                 json_object* poName = CPL_json_object_object_get(poMosaic, "name");
1124                 if( poName != nullptr && json_object_get_type(poName) == json_type_string )
1125                 {
1126                     pszName = json_object_get_string(poName);
1127                 }
1128 
1129                 json_object* poCoordinateSystem = CPL_json_object_object_get(poMosaic, "coordinate_system");
1130                 if( poCoordinateSystem && json_object_get_type(poCoordinateSystem) == json_type_string )
1131                 {
1132                     pszCoordinateSystem = json_object_get_string(poCoordinateSystem);
1133                 }
1134 
1135                 json_object* poDataType = CPL_json_object_object_get(poMosaic, "datatype");
1136                 if( poDataType && json_object_get_type(poDataType) == json_type_string &&
1137                     EQUAL(json_object_get_string(poDataType), "byte") &&
1138                     !CSLTestBoolean(CPLGetConfigOption("PL_MOSAIC_LIST_QUAD_DOWNLOAD_ONLY", "NO")) )
1139                 {
1140                     bAccessible = true; // through tile API
1141                 }
1142                 else
1143                 {
1144                     json_object* poQuadDownload = CPL_json_object_object_get(
1145                                                     poMosaic, "quad_download");
1146                     bAccessible = CPL_TO_BOOL(
1147                         json_object_get_boolean(poQuadDownload));
1148                 }
1149             }
1150 
1151             if( bAccessible && pszName && pszCoordinateSystem &&
1152                 EQUAL(pszCoordinateSystem, "EPSG:3857") )
1153             {
1154                 aosNameList.push_back(pszName);
1155             }
1156         }
1157 
1158         json_object_put(poObj);
1159     }
1160     return aosNameList;
1161 }
1162 
1163 /************************************************************************/
1164 /*                            GetProjectionRef()                       */
1165 /************************************************************************/
1166 
_GetProjectionRef()1167 const char* PLMosaicDataset::_GetProjectionRef()
1168 {
1169     return (pszWKT) ? pszWKT : "";
1170 }
1171 
1172 /************************************************************************/
1173 /*                            GetGeoTransform()                         */
1174 /************************************************************************/
1175 
GetGeoTransform(double * padfGeoTransform)1176 CPLErr PLMosaicDataset::GetGeoTransform(double* padfGeoTransform)
1177 {
1178     memcpy(padfGeoTransform, adfGeoTransform, 6 * sizeof(double));
1179     return ( bHasGeoTransform ) ? CE_None : CE_Failure;
1180 }
1181 
1182 /************************************************************************/
1183 /*                          formatTileName()                            */
1184 /************************************************************************/
1185 
formatTileName(int tile_x,int tile_y)1186 CPLString PLMosaicDataset::formatTileName(int tile_x, int tile_y)
1187 
1188 {
1189     return CPLSPrintf("%d-%d", tile_x, tile_y);
1190 }
1191 
1192 /************************************************************************/
1193 /*                          InsertNewDataset()                          */
1194 /************************************************************************/
1195 
InsertNewDataset(CPLString osKey,GDALDataset * poDS)1196 void PLMosaicDataset::InsertNewDataset(CPLString osKey, GDALDataset* poDS)
1197 {
1198     if( static_cast<int>( oMapLinkedDatasets.size() ) == nCacheMaxSize )
1199     {
1200         CPLDebug("PLMOSAIC", "Discarding older entry %s from cache",
1201                  psTail->osKey.c_str());
1202         oMapLinkedDatasets.erase(psTail->osKey);
1203         PLLinkedDataset* psNewTail = psTail->psPrev;
1204         psNewTail->psNext = nullptr;
1205         if( psTail->poDS )
1206             GDALClose( psTail->poDS );
1207         delete psTail;
1208         psTail = psNewTail;
1209     }
1210 
1211     PLLinkedDataset* psLinkedDataset = new PLLinkedDataset();
1212     if( psHead )
1213         psHead->psPrev = psLinkedDataset;
1214     psLinkedDataset->osKey = osKey;
1215     psLinkedDataset->psNext = psHead;
1216     psLinkedDataset->poDS = poDS;
1217     psHead = psLinkedDataset;
1218     if( psTail == nullptr )
1219         psTail = psHead;
1220     oMapLinkedDatasets[osKey] = psLinkedDataset;
1221 }
1222 
1223 /************************************************************************/
1224 /*                         OpenAndInsertNewDataset()                    */
1225 /************************************************************************/
1226 
OpenAndInsertNewDataset(CPLString osTmpFilename,CPLString osTilename)1227 GDALDataset* PLMosaicDataset::OpenAndInsertNewDataset(CPLString osTmpFilename,
1228                                                       CPLString osTilename)
1229 {
1230     const char* const apszAllowedDrivers[2] = { "GTiff", nullptr };
1231     GDALDataset* poDS = reinterpret_cast<GDALDataset *>(
1232         GDALOpenEx( osTmpFilename, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
1233                     apszAllowedDrivers, nullptr, nullptr ) );
1234     if( poDS != nullptr )
1235     {
1236         if( poDS->GetRasterXSize() != nQuadSize ||
1237             poDS->GetRasterYSize() != nQuadSize ||
1238             poDS->GetRasterCount() != 4 )
1239         {
1240             CPLError(CE_Failure, CPLE_AppDefined,
1241                      "Inconsistent metatile characteristics");
1242             GDALClose(poDS);
1243             poDS = nullptr;
1244         }
1245     }
1246     else
1247     {
1248         CPLError(CE_Failure, CPLE_AppDefined, "Invalid GTiff dataset: %s",
1249                  osTilename.c_str());
1250     }
1251 
1252     InsertNewDataset(osTilename, poDS);
1253     return poDS;
1254 }
1255 
1256 /************************************************************************/
1257 /*                            GetMetaTile()                             */
1258 /************************************************************************/
1259 
GetMetaTile(int tile_x,int tile_y)1260 GDALDataset* PLMosaicDataset::GetMetaTile(int tile_x, int tile_y)
1261 {
1262     const CPLString osTilename = formatTileName(tile_x, tile_y);
1263     std::map<CPLString,PLLinkedDataset*>::const_iterator it =
1264                                                     oMapLinkedDatasets.find(osTilename);
1265     if( it == oMapLinkedDatasets.end() )
1266     {
1267         CPLString osTmpFilename;
1268 
1269         const CPLString osMosaicPath(GetMosaicCachePath());
1270         osTmpFilename = CPLFormFilename(osMosaicPath,
1271                 CPLSPrintf("%s_%s.tif", osMosaic.c_str(), CPLGetFilename(osTilename)), nullptr);
1272         VSIStatBufL sStatBuf;
1273 
1274         CPLString osURL = osQuadsURL;
1275         osURL += osTilename;
1276         osURL += "/full";
1277 
1278         if( !osCachePathRoot.empty() && VSIStatL(osTmpFilename, &sStatBuf) == 0 )
1279         {
1280             if( bTrustCache )
1281             {
1282                 return OpenAndInsertNewDataset(osTmpFilename, osTilename);
1283             }
1284 
1285             CPLDebug("PLMOSAIC", "File %s exists. Checking if it is up-to-date...",
1286                      osTmpFilename.c_str());
1287             // Currently we only check by file size, which should be good enough
1288             // as the metatiles are compressed, so a change in content is likely
1289             // to cause a change in filesize. Use of a signature would be better
1290             // though if available in the metadata
1291             VSIStatBufL sRemoteTileStatBuf;
1292             char* pszEscapedURL = CPLEscapeString(
1293                 (osURL + "?api_key=" + osAPIKey).c_str(), -1, CPLES_URL );
1294             CPLString osVSICURLUrl(
1295                 STARTS_WITH(osURL, "/vsimem/") ? osURL :
1296                     "/vsicurl?use_head=no&url=" + CPLString(pszEscapedURL));
1297             CPLFree(pszEscapedURL);
1298             if( VSIStatL(osVSICURLUrl, &sRemoteTileStatBuf) == 0 &&
1299                 sRemoteTileStatBuf.st_size == sStatBuf.st_size )
1300             {
1301                 CPLDebug("PLMOSAIC", "Cached tile is up-to-date");
1302                 return OpenAndInsertNewDataset(osTmpFilename, osTilename);
1303             }
1304             else
1305             {
1306                 CPLDebug("PLMOSAIC", "Cached tile is not up-to-date");
1307                 VSIUnlink(osTmpFilename);
1308             }
1309         }
1310 
1311         // Fetch the GeoTIFF now
1312 
1313         CPLHTTPResult* psResult = Download(osURL, TRUE);
1314         if( psResult == nullptr )
1315         {
1316             InsertNewDataset(osTilename, nullptr);
1317             return nullptr;
1318         }
1319 
1320         CreateMosaicCachePathIfNecessary();
1321 
1322         VSILFILE* fp = osCachePathRoot.size() ? VSIFOpenL(osTmpFilename, "wb") : nullptr;
1323         if( fp )
1324         {
1325             VSIFWriteL(psResult->pabyData, 1, psResult->nDataLen, fp);
1326             VSIFCloseL(fp);
1327         }
1328         else
1329         {
1330             // In case there's no temporary path or it is not writable
1331             // use a in-memory dataset, and limit the cache to only one
1332             if( !osCachePathRoot.empty() && nCacheMaxSize > 1 )
1333             {
1334                 CPLError(CE_Failure, CPLE_AppDefined,
1335                          "Cannot write into %s. Using /vsimem and reduce cache to 1 entry",
1336                          osCachePathRoot.c_str());
1337                 FlushDatasetsCache();
1338                 nCacheMaxSize = 1;
1339             }
1340             osTmpFilename =
1341                 CPLSPrintf("/vsimem/single_tile_plmosaic_cache/%s/%d_%d.tif",
1342                            osMosaic.c_str(), tile_x, tile_y);
1343             fp = VSIFOpenL(osTmpFilename, "wb");
1344             if( fp )
1345             {
1346                 VSIFWriteL(psResult->pabyData, 1, psResult->nDataLen, fp);
1347                 VSIFCloseL(fp);
1348             }
1349         }
1350         CPLHTTPDestroyResult(psResult);
1351         GDALDataset* poDS = OpenAndInsertNewDataset(osTmpFilename, osTilename);
1352 
1353         if( STARTS_WITH(osTmpFilename, "/vsimem/single_tile_plmosaic_cache/") )
1354             VSIUnlink(osTilename);
1355 
1356         return poDS;
1357     }
1358 
1359     // Move link to head of MRU list
1360     PLLinkedDataset* psLinkedDataset = it->second;
1361     GDALDataset* poDS = psLinkedDataset->poDS;
1362     if( psLinkedDataset != psHead )
1363     {
1364         if( psLinkedDataset == psTail )
1365             psTail = psLinkedDataset->psPrev;
1366         if( psLinkedDataset->psPrev )
1367             psLinkedDataset->psPrev->psNext = psLinkedDataset->psNext;
1368         if( psLinkedDataset->psNext )
1369             psLinkedDataset->psNext->psPrev = psLinkedDataset->psPrev;
1370         psLinkedDataset->psNext = psHead;
1371         psLinkedDataset->psPrev = nullptr;
1372         psHead->psPrev = psLinkedDataset;
1373         psHead = psLinkedDataset;
1374     }
1375 
1376     return poDS;
1377 }
1378 
1379 /************************************************************************/
1380 /*                         GetLocationInfo()                            */
1381 /************************************************************************/
1382 
GetLocationInfo(int nPixel,int nLine)1383 const char* PLMosaicDataset::GetLocationInfo(int nPixel, int nLine)
1384 {
1385     int nBlockXSize, nBlockYSize;
1386     GetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
1387 
1388     const int nBlockXOff = nPixel / nBlockXSize;
1389     const int nBlockYOff = nLine / nBlockYSize;
1390     const int bottom_yblock = (nRasterYSize - nBlockYOff * nBlockYSize) / nBlockYSize - 1;
1391 
1392     const int meta_tile_x = nMetaTileXShift + (nBlockXOff * nBlockXSize) / nQuadSize;
1393     const int meta_tile_y = nMetaTileYShift + (bottom_yblock * nBlockYSize) / nQuadSize;
1394 
1395     CPLString osQuadURL = osQuadsURL;
1396     CPLString osTilename = formatTileName(meta_tile_x, meta_tile_y);
1397     osQuadURL += osTilename;
1398 
1399     if( meta_tile_x != nLastMetaTileX || meta_tile_y != nLastMetaTileY )
1400     {
1401         const CPLString osQuadScenesURL = osQuadURL + "/items";
1402 
1403         json_object_put(poLastItemsInformation);
1404         poLastItemsInformation = RunRequest(osQuadScenesURL, TRUE);
1405 
1406         nLastMetaTileX = meta_tile_x;
1407         nLastMetaTileY = meta_tile_y;
1408     }
1409 
1410     osLastRetGetLocationInfo.clear();
1411 
1412     CPLXMLNode* psRoot = CPLCreateXMLNode(nullptr, CXT_Element, "LocationInfo");
1413 
1414     if( poLastItemsInformation )
1415     {
1416         json_object* poItems = CPL_json_object_object_get(poLastItemsInformation, "items");
1417         if( poItems && json_object_get_type(poItems) == json_type_array &&
1418             json_object_array_length(poItems) != 0 )
1419         {
1420             CPLXMLNode* psScenes =
1421                 CPLCreateXMLNode(psRoot, CXT_Element, "Scenes");
1422             const auto nItemsLength = json_object_array_length(poItems);
1423             for(auto i = decltype(nItemsLength){0}; i < nItemsLength; i++ )
1424             {
1425                 json_object* poObj = json_object_array_get_idx(poItems, i);
1426                 if ( poObj && json_object_get_type(poObj) == json_type_object )
1427                 {
1428                     json_object* poLink = CPL_json_object_object_get(poObj, "link");
1429                     if( poLink )
1430                     {
1431                         CPLXMLNode* psScene = CPLCreateXMLNode(psScenes, CXT_Element, "Scene");
1432                         CPLXMLNode* psItem = CPLCreateXMLNode(psScene,
1433                                 CXT_Element, "link");
1434                         CPLCreateXMLNode(psItem, CXT_Text, json_object_get_string(poLink));
1435                     }
1436                 }
1437             }
1438         }
1439     }
1440 
1441     char* pszXML = CPLSerializeXMLTree(psRoot);
1442     CPLDestroyXMLNode(psRoot);
1443     osLastRetGetLocationInfo = pszXML;
1444     CPLFree(pszXML);
1445 
1446     return osLastRetGetLocationInfo.c_str();
1447 }
1448 
1449 /************************************************************************/
1450 /*                             IRasterIO()                              */
1451 /************************************************************************/
1452 
IRasterIO(GDALRWFlag eRWFlag,int nXOff,int nYOff,int nXSize,int nYSize,void * pData,int nBufXSize,int nBufYSize,GDALDataType eBufType,int nBandCount,int * panBandMap,GSpacing nPixelSpace,GSpacing nLineSpace,GSpacing nBandSpace,GDALRasterIOExtraArg * psExtraArg)1453 CPLErr  PLMosaicDataset::IRasterIO( GDALRWFlag eRWFlag,
1454                                int nXOff, int nYOff, int nXSize, int nYSize,
1455                                void * pData, int nBufXSize, int nBufYSize,
1456                                GDALDataType eBufType,
1457                                int nBandCount, int *panBandMap,
1458                                GSpacing nPixelSpace, GSpacing nLineSpace,
1459                                GSpacing nBandSpace,
1460                                GDALRasterIOExtraArg* psExtraArg)
1461 {
1462     if( bUseTMSForMain && !apoTMSDS.empty() )
1463         return apoTMSDS[0]->RasterIO( eRWFlag, nXOff, nYOff, nXSize, nYSize,
1464                                   pData, nBufXSize, nBufYSize,
1465                                   eBufType, nBandCount, panBandMap,
1466                                   nPixelSpace, nLineSpace, nBandSpace,
1467                                   psExtraArg );
1468 
1469     return BlockBasedRasterIO( eRWFlag, nXOff, nYOff, nXSize, nYSize,
1470                                pData, nBufXSize, nBufYSize,
1471                                eBufType, nBandCount, panBandMap,
1472                                nPixelSpace, nLineSpace, nBandSpace,
1473                                psExtraArg );
1474 }
1475 
1476 /************************************************************************/
1477 /*                      GDALRegister_PLMOSAIC()                         */
1478 /************************************************************************/
1479 
GDALRegister_PLMOSAIC()1480 void GDALRegister_PLMOSAIC()
1481 
1482 {
1483     if( GDALGetDriverByName( "PLMOSAIC" ) != nullptr )
1484         return;
1485 
1486     GDALDriver *poDriver = new GDALDriver();
1487 
1488     poDriver->SetDescription( "PLMOSAIC" );
1489     poDriver->SetMetadataItem( GDAL_DCAP_RASTER, "YES" );
1490     poDriver->SetMetadataItem( GDAL_DMD_LONGNAME,
1491                                "Planet Labs Mosaics API" );
1492     poDriver->SetMetadataItem( GDAL_DMD_HELPTOPIC,
1493                                "drivers/raster/plmosaic.html" );
1494 
1495     poDriver->SetMetadataItem( GDAL_DMD_CONNECTION_PREFIX, "PLMOSAIC:" );
1496 
1497     poDriver->SetMetadataItem( GDAL_DMD_OPENOPTIONLIST,
1498 "<OpenOptionList>"
1499 "  <Option name='API_KEY' type='string' description='Account API key' required='true'/>"
1500 "  <Option name='MOSAIC' type='string' description='Mosaic name'/>"
1501 "  <Option name='CACHE_PATH' type='string' description='Directory where to put cached quads'/>"
1502 "  <Option name='TRUST_CACHE' type='boolean' description='Whether already cached quads should be trusted as the most recent version' default='NO'/>"
1503 "  <Option name='USE_TILES' type='boolean' description='Whether to use the tile API even for full resolution data (only for Byte mosaics)' default='NO'/>"
1504 "</OpenOptionList>" );
1505 
1506     poDriver->pfnIdentify = PLMosaicDataset::Identify;
1507     poDriver->pfnOpen = PLMosaicDataset::Open;
1508 
1509     GetGDALDriverManager()->RegisterDriver( poDriver );
1510 }
1511