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