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