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