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