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 : ~PLMosaicDataset() override;
102 :
103 : static int Identify(GDALOpenInfo *poOpenInfo);
104 : static GDALDataset *Open(GDALOpenInfo *);
105 :
106 : CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize,
107 : int nYSize, void *pData, int nBufXSize, int nBufYSize,
108 : GDALDataType eBufType, int nBandCount,
109 : BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
110 : GSpacing nLineSpace, GSpacing nBandSpace,
111 : GDALRasterIOExtraArg *psExtraArg) override;
112 :
113 : CPLErr FlushCache(bool bAtClosing) override;
114 :
115 : const OGRSpatialReference *GetSpatialRef() const override;
116 : CPLErr GetGeoTransform(GDALGeoTransform >) const override;
117 :
118 : GDALDataset *GetMetaTile(int tile_x, int tile_y);
119 : };
120 :
121 : /************************************************************************/
122 : /* ==================================================================== */
123 : /* PLMosaicRasterBand */
124 : /* ==================================================================== */
125 : /************************************************************************/
126 :
127 : class PLMosaicRasterBand final : public GDALRasterBand
128 : {
129 : friend class PLMosaicDataset;
130 :
131 : public:
132 : PLMosaicRasterBand(PLMosaicDataset *poDS, int nBand,
133 : GDALDataType eDataType);
134 :
135 : CPLErr IReadBlock(int, int, void *) override;
136 : CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize,
137 : int nYSize, void *pData, int nBufXSize, int nBufYSize,
138 : GDALDataType eBufType, GSpacing nPixelSpace,
139 : GSpacing nLineSpace,
140 : GDALRasterIOExtraArg *psExtraArg) override;
141 :
142 : virtual const char *GetMetadataItem(const char *pszName,
143 : const char *pszDomain = "") override;
144 :
145 : GDALColorInterp GetColorInterpretation() override;
146 :
147 : int GetOverviewCount() override;
148 : GDALRasterBand *GetOverview(int iOvrLevel) override;
149 : };
150 :
151 : /************************************************************************/
152 : /* PLMosaicRasterBand() */
153 : /************************************************************************/
154 :
155 48 : PLMosaicRasterBand::PLMosaicRasterBand(PLMosaicDataset *poDSIn, int nBandIn,
156 48 : GDALDataType eDataTypeIn)
157 :
158 : {
159 48 : eDataType = eDataTypeIn;
160 48 : nBlockXSize = 256;
161 48 : nBlockYSize = 256;
162 :
163 48 : poDS = poDSIn;
164 48 : nBand = nBandIn;
165 :
166 48 : if (eDataType == GDT_UInt16)
167 : {
168 4 : if (nBand <= 3)
169 3 : SetMetadataItem("NBITS", "12", "IMAGE_STRUCTURE");
170 : }
171 48 : }
172 :
173 : /************************************************************************/
174 : /* IReadBlock() */
175 : /************************************************************************/
176 :
177 63 : CPLErr PLMosaicRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
178 : void *pImage)
179 : {
180 63 : PLMosaicDataset *poMOSDS = cpl::down_cast<PLMosaicDataset *>(poDS);
181 :
182 : #ifdef DEBUG_VERBOSE
183 : CPLDebug("PLMOSAIC", "IReadBlock(band=%d, x=%d, y=%d)", nBand, nBlockYOff,
184 : nBlockYOff);
185 : #endif
186 :
187 63 : if (poMOSDS->bUseTMSForMain && !poMOSDS->apoTMSDS.empty())
188 1 : return poMOSDS->apoTMSDS[0]->GetRasterBand(nBand)->ReadBlock(
189 1 : nBlockXOff, nBlockYOff, pImage);
190 :
191 62 : const int bottom_yblock =
192 62 : (nRasterYSize - nBlockYOff * nBlockYSize) / nBlockYSize - 1;
193 :
194 62 : const int meta_tile_x = poMOSDS->nMetaTileXShift +
195 62 : (nBlockXOff * nBlockXSize) / poMOSDS->nQuadSize;
196 62 : const int meta_tile_y = poMOSDS->nMetaTileYShift +
197 62 : (bottom_yblock * nBlockYSize) / poMOSDS->nQuadSize;
198 62 : const int sub_tile_x = nBlockXOff % (poMOSDS->nQuadSize / nBlockXSize);
199 62 : const int sub_tile_y = nBlockYOff % (poMOSDS->nQuadSize / nBlockYSize);
200 :
201 62 : GDALDataset *poMetaTileDS = poMOSDS->GetMetaTile(meta_tile_x, meta_tile_y);
202 62 : if (poMetaTileDS == nullptr)
203 : {
204 38 : memset(pImage, 0,
205 38 : static_cast<size_t>(nBlockXSize) * nBlockYSize *
206 38 : GDALGetDataTypeSizeBytes(eDataType));
207 38 : return CE_None;
208 : }
209 :
210 24 : return poMetaTileDS->GetRasterBand(nBand)->RasterIO(
211 24 : GF_Read, sub_tile_x * nBlockXSize, sub_tile_y * nBlockYSize,
212 : nBlockXSize, nBlockYSize, pImage, nBlockXSize, nBlockYSize, eDataType,
213 24 : 0, 0, nullptr);
214 : }
215 :
216 : /************************************************************************/
217 : /* IRasterIO() */
218 : /************************************************************************/
219 :
220 63 : CPLErr PLMosaicRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
221 : int nXSize, int nYSize, void *pData,
222 : int nBufXSize, int nBufYSize,
223 : GDALDataType eBufType,
224 : GSpacing nPixelSpace, GSpacing nLineSpace,
225 : GDALRasterIOExtraArg *psExtraArg)
226 : {
227 63 : PLMosaicDataset *poMOSDS = cpl::down_cast<PLMosaicDataset *>(poDS);
228 63 : if (poMOSDS->bUseTMSForMain && !poMOSDS->apoTMSDS.empty())
229 1 : return poMOSDS->apoTMSDS[0]->GetRasterBand(nBand)->RasterIO(
230 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
231 1 : eBufType, nPixelSpace, nLineSpace, psExtraArg);
232 :
233 62 : return GDALRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
234 : pData, nBufXSize, nBufYSize, eBufType,
235 62 : nPixelSpace, nLineSpace, psExtraArg);
236 : }
237 :
238 : /************************************************************************/
239 : /* GetMetadataItem() */
240 : /************************************************************************/
241 :
242 4 : const char *PLMosaicRasterBand::GetMetadataItem(const char *pszName,
243 : const char *pszDomain)
244 : {
245 4 : PLMosaicDataset *poMOSDS = cpl::down_cast<PLMosaicDataset *>(poDS);
246 : int nPixel, nLine;
247 4 : if (poMOSDS->bQuadDownload && pszName != nullptr && pszDomain != nullptr &&
248 12 : EQUAL(pszDomain, "LocationInfo") &&
249 4 : sscanf(pszName, "Pixel_%d_%d", &nPixel, &nLine) == 2)
250 : {
251 4 : return poMOSDS->GetLocationInfo(nPixel, nLine);
252 : }
253 :
254 0 : return GDALRasterBand::GetMetadataItem(pszName, pszDomain);
255 : }
256 :
257 : /************************************************************************/
258 : /* GetOverviewCount() */
259 : /************************************************************************/
260 :
261 3 : int PLMosaicRasterBand::GetOverviewCount()
262 : {
263 3 : PLMosaicDataset *poGDS = cpl::down_cast<PLMosaicDataset *>(poDS);
264 3 : return std::max(0, static_cast<int>(poGDS->apoTMSDS.size()) - 1);
265 : }
266 :
267 : /************************************************************************/
268 : /* GetOverview() */
269 : /************************************************************************/
270 :
271 4 : GDALRasterBand *PLMosaicRasterBand::GetOverview(int iOvrLevel)
272 : {
273 4 : PLMosaicDataset *poGDS = cpl::down_cast<PLMosaicDataset *>(poDS);
274 7 : if (iOvrLevel < 0 ||
275 3 : iOvrLevel >= static_cast<int>(poGDS->apoTMSDS.size()) - 1)
276 3 : return nullptr;
277 :
278 1 : poGDS->CreateMosaicCachePathIfNecessary();
279 :
280 1 : return poGDS->apoTMSDS[iOvrLevel + 1]->GetRasterBand(nBand);
281 : }
282 :
283 : /************************************************************************/
284 : /* GetColorInterpretation() */
285 : /************************************************************************/
286 :
287 0 : GDALColorInterp PLMosaicRasterBand::GetColorInterpretation()
288 : {
289 0 : switch (nBand)
290 : {
291 0 : case 1:
292 0 : return GCI_RedBand;
293 0 : case 2:
294 0 : return GCI_GreenBand;
295 0 : case 3:
296 0 : return GCI_BlueBand;
297 0 : case 4:
298 0 : return GCI_AlphaBand;
299 0 : default:
300 0 : CPLAssert(false);
301 : return GCI_GrayIndex;
302 : }
303 : }
304 :
305 : /************************************************************************/
306 : /* ==================================================================== */
307 : /* PLMosaicDataset */
308 : /* ==================================================================== */
309 : /************************************************************************/
310 :
311 : /************************************************************************/
312 : /* PLMosaicDataset() */
313 : /************************************************************************/
314 :
315 30 : PLMosaicDataset::PLMosaicDataset()
316 : : bMustCleanPersistent(FALSE), bTrustCache(FALSE), nQuadSize(0),
317 : bHasGeoTransform(FALSE), nZoomLevelMax(0), bUseTMSForMain(FALSE),
318 : nCacheMaxSize(10), psHead(nullptr), psTail(nullptr), nLastMetaTileX(-1),
319 30 : nLastMetaTileY(-1)
320 : {
321 30 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
322 :
323 30 : SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
324 30 : osCachePathRoot = CPLGetPathSafe(CPLGenerateTempFilenameSafe("").c_str());
325 30 : }
326 :
327 : /************************************************************************/
328 : /* ~PLMosaicDataset() */
329 : /************************************************************************/
330 :
331 60 : PLMosaicDataset::~PLMosaicDataset()
332 :
333 : {
334 30 : PLMosaicDataset::FlushCache(true);
335 170 : for (auto &poDS : apoTMSDS)
336 140 : delete poDS;
337 30 : if (poLastItemsInformation)
338 0 : json_object_put(poLastItemsInformation);
339 30 : if (bMustCleanPersistent)
340 : {
341 28 : char **papszOptions = CSLSetNameValue(nullptr, "CLOSE_PERSISTENT",
342 : CPLSPrintf("PLMOSAIC:%p", this));
343 28 : CPLHTTPDestroyResult(CPLHTTPFetch(osBaseURL, papszOptions));
344 28 : CSLDestroy(papszOptions);
345 : }
346 60 : }
347 :
348 : /************************************************************************/
349 : /* FlushDatasetsCache() */
350 : /************************************************************************/
351 :
352 36 : void PLMosaicDataset::FlushDatasetsCache()
353 : {
354 55 : for (PLLinkedDataset *psIter = psHead; psIter != nullptr;)
355 : {
356 19 : PLLinkedDataset *psNext = psIter->psNext;
357 19 : if (psIter->poDS)
358 7 : GDALClose(psIter->poDS);
359 19 : delete psIter;
360 19 : psIter = psNext;
361 : }
362 36 : psHead = nullptr;
363 36 : psTail = nullptr;
364 36 : oMapLinkedDatasets.clear();
365 36 : }
366 :
367 : /************************************************************************/
368 : /* FlushCache() */
369 : /************************************************************************/
370 :
371 35 : CPLErr PLMosaicDataset::FlushCache(bool bAtClosing)
372 : {
373 35 : FlushDatasetsCache();
374 :
375 35 : nLastMetaTileX = -1;
376 35 : nLastMetaTileY = -1;
377 35 : if (poLastItemsInformation)
378 2 : json_object_put(poLastItemsInformation);
379 35 : poLastItemsInformation = nullptr;
380 35 : osLastRetGetLocationInfo.clear();
381 :
382 35 : return GDALDataset::FlushCache(bAtClosing);
383 : }
384 :
385 : /************************************************************************/
386 : /* Identify() */
387 : /************************************************************************/
388 :
389 58961 : int PLMosaicDataset::Identify(GDALOpenInfo *poOpenInfo)
390 :
391 : {
392 58961 : return STARTS_WITH_CI(poOpenInfo->pszFilename, "PLMOSAIC:");
393 : }
394 :
395 : /************************************************************************/
396 : /* GetBaseHTTPOptions() */
397 : /************************************************************************/
398 :
399 56 : char **PLMosaicDataset::GetBaseHTTPOptions()
400 : {
401 56 : bMustCleanPersistent = TRUE;
402 :
403 : char **papszOptions =
404 56 : CSLAddString(nullptr, CPLSPrintf("PERSISTENT=PLMOSAIC:%p", this));
405 :
406 : /* Ensure the PLMosaic driver uses a unique default user agent to help
407 : * identify usage. */
408 56 : CPLString osUserAgent = CPLGetConfigOption("GDAL_HTTP_USERAGENT", "");
409 56 : if (osUserAgent.empty())
410 56 : papszOptions = CSLAddString(
411 : papszOptions, CPLSPrintf("USERAGENT=PLMosaic Driver GDAL/%d.%d.%d",
412 : GDAL_VERSION_MAJOR, GDAL_VERSION_MINOR,
413 : GDAL_VERSION_REV));
414 :
415 : /* Use basic auth, rather than Authorization headers since curl would
416 : * forward it to S3 */
417 : papszOptions =
418 56 : CSLAddString(papszOptions, CPLSPrintf("USERPWD=%s:", osAPIKey.c_str()));
419 :
420 112 : return papszOptions;
421 : }
422 :
423 : /************************************************************************/
424 : /* Download() */
425 : /************************************************************************/
426 :
427 56 : CPLHTTPResult *PLMosaicDataset::Download(const char *pszURL, int bQuiet404Error)
428 : {
429 56 : char **papszOptions = CSLAddString(GetBaseHTTPOptions(), nullptr);
430 56 : CPLHTTPResult *psResult = nullptr;
431 56 : if (STARTS_WITH(osBaseURL, "/vsimem/") && STARTS_WITH(pszURL, "/vsimem/"))
432 : {
433 18 : CPLDebug("PLSCENES", "Fetching %s", pszURL);
434 : psResult = reinterpret_cast<CPLHTTPResult *>(
435 18 : CPLCalloc(1, sizeof(CPLHTTPResult)));
436 18 : vsi_l_offset nDataLength = 0;
437 36 : CPLString osURL(pszURL);
438 18 : if (osURL.back() == '/')
439 1 : osURL.pop_back();
440 18 : GByte *pabyBuf = VSIGetMemFileBuffer(osURL, &nDataLength, FALSE);
441 18 : if (pabyBuf)
442 : {
443 16 : psResult->pabyData = reinterpret_cast<GByte *>(
444 16 : VSIMalloc(1 + static_cast<size_t>(nDataLength)));
445 16 : if (psResult->pabyData)
446 : {
447 16 : memcpy(psResult->pabyData, pabyBuf,
448 : static_cast<size_t>(nDataLength));
449 16 : psResult->pabyData[nDataLength] = 0;
450 16 : psResult->nDataLen = static_cast<int>(nDataLength);
451 : }
452 : }
453 : else
454 : {
455 2 : psResult->pszErrBuf =
456 2 : CPLStrdup(CPLSPrintf("Error 404. Cannot find %s", pszURL));
457 : }
458 : }
459 : else
460 : {
461 38 : if (bQuiet404Error)
462 25 : CPLPushErrorHandler(CPLQuietErrorHandler);
463 38 : psResult = CPLHTTPFetch(pszURL, papszOptions);
464 38 : if (bQuiet404Error)
465 25 : CPLPopErrorHandler();
466 : }
467 56 : CSLDestroy(papszOptions);
468 :
469 56 : if (psResult->pszErrBuf != nullptr)
470 : {
471 19 : if (!(bQuiet404Error && strstr(psResult->pszErrBuf, "404")))
472 : {
473 2 : CPLError(CE_Failure, CPLE_AppDefined, "%s",
474 2 : psResult->pabyData
475 : ? reinterpret_cast<const char *>(psResult->pabyData)
476 : : psResult->pszErrBuf);
477 : }
478 19 : CPLHTTPDestroyResult(psResult);
479 19 : return nullptr;
480 : }
481 :
482 37 : if (psResult->pabyData == nullptr)
483 : {
484 0 : CPLError(CE_Failure, CPLE_AppDefined,
485 : "Empty content returned by server");
486 0 : CPLHTTPDestroyResult(psResult);
487 0 : return nullptr;
488 : }
489 :
490 37 : return psResult;
491 : }
492 :
493 : /************************************************************************/
494 : /* RunRequest() */
495 : /************************************************************************/
496 :
497 32 : json_object *PLMosaicDataset::RunRequest(const char *pszURL, int bQuiet404Error)
498 : {
499 32 : CPLHTTPResult *psResult = Download(pszURL, bQuiet404Error);
500 32 : if (psResult == nullptr)
501 : {
502 3 : return nullptr;
503 : }
504 :
505 29 : json_object *poObj = nullptr;
506 29 : const char *pszText = reinterpret_cast<const char *>(psResult->pabyData);
507 29 : if (!OGRJSonParse(pszText, &poObj, true))
508 : {
509 3 : CPLHTTPDestroyResult(psResult);
510 3 : return nullptr;
511 : }
512 :
513 26 : CPLHTTPDestroyResult(psResult);
514 :
515 26 : if (json_object_get_type(poObj) != json_type_object)
516 : {
517 0 : CPLError(CE_Failure, CPLE_AppDefined,
518 : "Return is not a JSON dictionary");
519 0 : json_object_put(poObj);
520 0 : poObj = nullptr;
521 : }
522 :
523 26 : return poObj;
524 : }
525 :
526 : /************************************************************************/
527 : /* PLMosaicGetParameter() */
528 : /************************************************************************/
529 :
530 141 : static CPLString PLMosaicGetParameter(GDALOpenInfo *poOpenInfo,
531 : char **papszOptions, const char *pszName,
532 : const char *pszDefaultVal)
533 : {
534 : return CSLFetchNameValueDef(
535 : papszOptions, pszName,
536 141 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, pszName,
537 141 : pszDefaultVal));
538 : }
539 :
540 : /************************************************************************/
541 : /* Open() */
542 : /************************************************************************/
543 :
544 30 : GDALDataset *PLMosaicDataset::Open(GDALOpenInfo *poOpenInfo)
545 :
546 : {
547 30 : if (!Identify(poOpenInfo))
548 0 : return nullptr;
549 :
550 30 : PLMosaicDataset *poDS = new PLMosaicDataset();
551 :
552 : poDS->osBaseURL = CPLGetConfigOption(
553 30 : "PL_URL", "https://api.planet.com/basemaps/v1/mosaics");
554 :
555 60 : char **papszOptions = CSLTokenizeStringComplex(
556 30 : poOpenInfo->pszFilename + strlen("PLMosaic:"), ",", TRUE, FALSE);
557 37 : for (char **papszIter = papszOptions; papszIter && *papszIter; papszIter++)
558 : {
559 8 : char *pszKey = nullptr;
560 8 : const char *pszValue = CPLParseNameValue(*papszIter, &pszKey);
561 8 : if (pszValue != nullptr)
562 : {
563 8 : if (!EQUAL(pszKey, "api_key") && !EQUAL(pszKey, "mosaic") &&
564 3 : !EQUAL(pszKey, "cache_path") && !EQUAL(pszKey, "trust_cache") &&
565 1 : !EQUAL(pszKey, "use_tiles"))
566 : {
567 1 : CPLError(CE_Failure, CPLE_NotSupported, "Unsupported option %s",
568 : pszKey);
569 1 : CPLFree(pszKey);
570 1 : delete poDS;
571 1 : CSLDestroy(papszOptions);
572 1 : return nullptr;
573 : }
574 7 : CPLFree(pszKey);
575 : }
576 : }
577 :
578 58 : poDS->osAPIKey = PLMosaicGetParameter(poOpenInfo, papszOptions, "api_key",
579 29 : CPLGetConfigOption("PL_API_KEY", ""));
580 :
581 29 : if (poDS->osAPIKey.empty())
582 : {
583 1 : CPLError(
584 : CE_Failure, CPLE_AppDefined,
585 : "Missing PL_API_KEY configuration option or API_KEY open option");
586 1 : delete poDS;
587 1 : CSLDestroy(papszOptions);
588 1 : return nullptr;
589 : }
590 :
591 : poDS->osMosaic =
592 28 : PLMosaicGetParameter(poOpenInfo, papszOptions, "mosaic", "");
593 :
594 : poDS->osCachePathRoot =
595 56 : PLMosaicGetParameter(poOpenInfo, papszOptions, "cache_path",
596 28 : CPLGetConfigOption("PL_CACHE_PATH", ""));
597 :
598 28 : poDS->bTrustCache = CPLTestBool(
599 56 : PLMosaicGetParameter(poOpenInfo, papszOptions, "trust_cache", "FALSE"));
600 :
601 28 : poDS->bUseTMSForMain = CPLTestBool(
602 56 : PLMosaicGetParameter(poOpenInfo, papszOptions, "use_tiles", "FALSE"));
603 :
604 28 : CSLDestroy(papszOptions);
605 28 : papszOptions = nullptr;
606 :
607 28 : if (!poDS->osMosaic.empty())
608 : {
609 20 : if (!poDS->OpenMosaic())
610 : {
611 8 : delete poDS;
612 8 : poDS = nullptr;
613 : }
614 : }
615 : else
616 : {
617 16 : auto aosNameList = poDS->ListSubdatasets();
618 8 : if (aosNameList.empty())
619 : {
620 5 : delete poDS;
621 5 : poDS = nullptr;
622 : }
623 3 : else if (aosNameList.size() == 1)
624 : {
625 4 : const CPLString osOldFilename(poOpenInfo->pszFilename);
626 : const CPLString osMosaicConnectionString =
627 4 : CPLSPrintf("PLMOSAIC:mosaic=%s", aosNameList[0].c_str());
628 2 : delete poDS;
629 : GDALOpenInfo oOpenInfo(osMosaicConnectionString.c_str(),
630 4 : GA_ReadOnly);
631 2 : oOpenInfo.papszOpenOptions = poOpenInfo->papszOpenOptions;
632 2 : poDS = cpl::down_cast<PLMosaicDataset *>(Open(&oOpenInfo));
633 2 : if (poDS)
634 2 : poDS->SetDescription(osOldFilename);
635 : }
636 : else
637 : {
638 2 : CPLStringList aosSubdatasets;
639 3 : for (const auto &osName : aosNameList)
640 : {
641 2 : const int nDatasetIdx = aosSubdatasets.Count() / 2 + 1;
642 : aosSubdatasets.AddNameValue(
643 : CPLSPrintf("SUBDATASET_%d_NAME", nDatasetIdx),
644 2 : CPLSPrintf("PLMOSAIC:mosaic=%s", osName.c_str()));
645 : aosSubdatasets.AddNameValue(
646 : CPLSPrintf("SUBDATASET_%d_DESC", nDatasetIdx),
647 2 : CPLSPrintf("Mosaic %s", osName.c_str()));
648 : }
649 1 : poDS->SetMetadata(aosSubdatasets.List(), "SUBDATASETS");
650 : }
651 : }
652 :
653 28 : if (poDS)
654 15 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
655 :
656 28 : return poDS;
657 : }
658 :
659 : /************************************************************************/
660 : /* ReplaceSubString() */
661 : /************************************************************************/
662 :
663 36 : static void ReplaceSubString(CPLString &osTarget, CPLString osPattern,
664 : CPLString osReplacement)
665 :
666 : {
667 : // Assumes only one occurrence of osPattern.
668 36 : size_t pos = osTarget.find(osPattern);
669 36 : if (pos == CPLString::npos)
670 0 : return;
671 :
672 36 : osTarget.replace(pos, osPattern.size(), osReplacement);
673 : }
674 :
675 : /************************************************************************/
676 : /* GetMosaicCachePath() */
677 : /************************************************************************/
678 :
679 32 : CPLString PLMosaicDataset::GetMosaicCachePath()
680 : {
681 32 : if (!osCachePathRoot.empty())
682 : {
683 : const CPLString osCachePath(
684 31 : CPLFormFilenameSafe(osCachePathRoot, "plmosaic_cache", nullptr));
685 62 : return CPLFormFilenameSafe(osCachePath, osMosaic, nullptr);
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 : CPLFormFilenameSafe(osCachePathRoot, "plmosaic_cache", nullptr));
700 : const CPLString osMosaicPath(
701 16 : CPLFormFilenameSafe(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 : m_gt[0] = GM_ORIGIN;
861 12 : m_gt[1] = dfResolution;
862 12 : m_gt[2] = 0;
863 12 : m_gt[3] = -GM_ORIGIN;
864 12 : m_gt[4] = 0;
865 12 : m_gt[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 : m_gt[0] = xmin;
894 1 : m_gt[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>(0.5 + (m_gt[0] - GM_ORIGIN) /
987 : dfThisResolution);
988 140 : nDstXOff = 0;
989 :
990 140 : nSrcYOff = static_cast<int>(0.5 + (-GM_ORIGIN - m_gt[3]) /
991 : dfThisResolution);
992 140 : nDstYOff = 0;
993 :
994 700 : for (int iBand = 1; iBand <= 4; iBand++)
995 : {
996 : VRTSourcedRasterBandH hVRTBand =
997 560 : reinterpret_cast<VRTSourcedRasterBandH>(
998 : GDALGetRasterBand(hVRTDS, iBand));
999 560 : VRTAddSimpleSource(
1000 : hVRTBand, GDALGetRasterBand(poTMSDS, iBand),
1001 : nSrcXOff, nSrcYOff, nOvrXSize, nOvrYSize, nDstXOff,
1002 : nDstYOff, nOvrXSize, nOvrYSize, "NEAR",
1003 : VRT_NODATA_UNSET);
1004 : }
1005 140 : poTMSDS->Dereference();
1006 :
1007 140 : apoTMSDS.push_back(GDALDataset::FromHandle(hVRTDS));
1008 : }
1009 :
1010 140 : if (nOvrXSize < 256 && nOvrYSize < 256)
1011 1 : break;
1012 : }
1013 : }
1014 : }
1015 :
1016 12 : if (bUseTMSForMain && apoTMSDS.empty())
1017 : {
1018 1 : CPLError(CE_Failure, CPLE_NotSupported,
1019 : "Cannot find tile definition, so use_tiles will be ignored");
1020 1 : bUseTMSForMain = FALSE;
1021 : }
1022 :
1023 60 : for (int i = 0; i < 4; i++)
1024 48 : SetBand(i + 1, new PLMosaicRasterBand(this, i + 1, eDT));
1025 :
1026 : json_object *poFirstAcquired =
1027 12 : CPL_json_object_object_get(poMosaic, "first_acquired");
1028 24 : if (poFirstAcquired != nullptr &&
1029 12 : json_object_get_type(poFirstAcquired) == json_type_string)
1030 : {
1031 12 : SetMetadataItem("FIRST_ACQUIRED",
1032 12 : json_object_get_string(poFirstAcquired));
1033 : }
1034 : json_object *poLastAcquired =
1035 12 : CPL_json_object_object_get(poMosaic, "last_acquired");
1036 24 : if (poLastAcquired != nullptr &&
1037 12 : json_object_get_type(poLastAcquired) == json_type_string)
1038 : {
1039 12 : SetMetadataItem("LAST_ACQUIRED",
1040 12 : json_object_get_string(poLastAcquired));
1041 : }
1042 12 : json_object *poName = CPL_json_object_object_get(poMosaic, "name");
1043 12 : if (poName != nullptr && json_object_get_type(poName) == json_type_string)
1044 : {
1045 12 : SetMetadataItem("NAME", json_object_get_string(poName));
1046 : }
1047 :
1048 12 : json_object_put(poObj);
1049 12 : return TRUE;
1050 : }
1051 :
1052 : /************************************************************************/
1053 : /* ListSubdatasets() */
1054 : /************************************************************************/
1055 :
1056 8 : std::vector<CPLString> PLMosaicDataset::ListSubdatasets()
1057 : {
1058 8 : std::vector<CPLString> aosNameList;
1059 16 : CPLString osURL(osBaseURL);
1060 13 : while (osURL.size())
1061 : {
1062 9 : json_object *poObj = RunRequest(osURL);
1063 9 : if (poObj == nullptr)
1064 : {
1065 3 : return aosNameList;
1066 : }
1067 :
1068 6 : osURL = "";
1069 6 : json_object *poLinks = CPL_json_object_object_get(poObj, "_links");
1070 8 : if (poLinks != nullptr &&
1071 2 : json_object_get_type(poLinks) == json_type_object)
1072 : {
1073 2 : json_object *poNext = CPL_json_object_object_get(poLinks, "_next");
1074 3 : if (poNext != nullptr &&
1075 1 : json_object_get_type(poNext) == json_type_string)
1076 : {
1077 1 : osURL = json_object_get_string(poNext);
1078 : }
1079 : }
1080 :
1081 6 : json_object *poMosaics = CPL_json_object_object_get(poObj, "mosaics");
1082 11 : if (poMosaics == nullptr ||
1083 5 : json_object_get_type(poMosaics) != json_type_array)
1084 : {
1085 1 : json_object_put(poObj);
1086 1 : return aosNameList;
1087 : }
1088 :
1089 5 : const auto nMosaics = json_object_array_length(poMosaics);
1090 10 : for (auto i = decltype(nMosaics){0}; i < nMosaics; i++)
1091 : {
1092 5 : const char *pszName = nullptr;
1093 5 : const char *pszCoordinateSystem = nullptr;
1094 5 : json_object *poMosaic = json_object_array_get_idx(poMosaics, i);
1095 5 : bool bAccessible = false;
1096 5 : if (poMosaic && json_object_get_type(poMosaic) == json_type_object)
1097 : {
1098 : json_object *poName =
1099 5 : CPL_json_object_object_get(poMosaic, "name");
1100 10 : if (poName != nullptr &&
1101 5 : json_object_get_type(poName) == json_type_string)
1102 : {
1103 5 : pszName = json_object_get_string(poName);
1104 : }
1105 :
1106 : json_object *poCoordinateSystem =
1107 5 : CPL_json_object_object_get(poMosaic, "coordinate_system");
1108 10 : if (poCoordinateSystem &&
1109 5 : json_object_get_type(poCoordinateSystem) ==
1110 : json_type_string)
1111 : {
1112 : pszCoordinateSystem =
1113 5 : json_object_get_string(poCoordinateSystem);
1114 : }
1115 :
1116 : json_object *poDataType =
1117 5 : CPL_json_object_object_get(poMosaic, "datatype");
1118 5 : if (poDataType &&
1119 0 : json_object_get_type(poDataType) == json_type_string &&
1120 5 : EQUAL(json_object_get_string(poDataType), "byte") &&
1121 0 : !CSLTestBoolean(CPLGetConfigOption(
1122 : "PL_MOSAIC_LIST_QUAD_DOWNLOAD_ONLY", "NO")))
1123 : {
1124 0 : bAccessible = true; // through tile API
1125 : }
1126 : else
1127 : {
1128 : json_object *poQuadDownload =
1129 5 : CPL_json_object_object_get(poMosaic, "quad_download");
1130 : bAccessible =
1131 5 : CPL_TO_BOOL(json_object_get_boolean(poQuadDownload));
1132 : }
1133 : }
1134 :
1135 5 : if (bAccessible && pszName && pszCoordinateSystem &&
1136 5 : EQUAL(pszCoordinateSystem, "EPSG:3857"))
1137 : {
1138 4 : aosNameList.push_back(pszName);
1139 : }
1140 : }
1141 :
1142 5 : json_object_put(poObj);
1143 : }
1144 4 : return aosNameList;
1145 : }
1146 :
1147 : /************************************************************************/
1148 : /* GetSpatialRef() */
1149 : /************************************************************************/
1150 :
1151 1 : const OGRSpatialReference *PLMosaicDataset::GetSpatialRef() const
1152 :
1153 : {
1154 1 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
1155 : }
1156 :
1157 : /************************************************************************/
1158 : /* GetGeoTransform() */
1159 : /************************************************************************/
1160 :
1161 2 : CPLErr PLMosaicDataset::GetGeoTransform(GDALGeoTransform >) const
1162 : {
1163 2 : gt = m_gt;
1164 2 : return (bHasGeoTransform) ? CE_None : CE_Failure;
1165 : }
1166 :
1167 : /************************************************************************/
1168 : /* formatTileName() */
1169 : /************************************************************************/
1170 :
1171 66 : CPLString PLMosaicDataset::formatTileName(int tile_x, int tile_y)
1172 :
1173 : {
1174 66 : return CPLSPrintf("%d-%d", tile_x, tile_y);
1175 : }
1176 :
1177 : /************************************************************************/
1178 : /* InsertNewDataset() */
1179 : /************************************************************************/
1180 :
1181 25 : void PLMosaicDataset::InsertNewDataset(const CPLString &osKey,
1182 : GDALDataset *poDS)
1183 : {
1184 25 : if (static_cast<int>(oMapLinkedDatasets.size()) == nCacheMaxSize)
1185 : {
1186 6 : CPLDebug("PLMOSAIC", "Discarding older entry %s from cache",
1187 6 : psTail->osKey.c_str());
1188 6 : oMapLinkedDatasets.erase(psTail->osKey);
1189 6 : PLLinkedDataset *psNewTail = psTail->psPrev;
1190 6 : psNewTail->psNext = nullptr;
1191 6 : if (psTail->poDS)
1192 0 : GDALClose(psTail->poDS);
1193 6 : delete psTail;
1194 6 : psTail = psNewTail;
1195 : }
1196 :
1197 25 : PLLinkedDataset *psLinkedDataset = new PLLinkedDataset();
1198 25 : if (psHead)
1199 15 : psHead->psPrev = psLinkedDataset;
1200 25 : psLinkedDataset->osKey = osKey;
1201 25 : psLinkedDataset->psNext = psHead;
1202 25 : psLinkedDataset->poDS = poDS;
1203 25 : psHead = psLinkedDataset;
1204 25 : if (psTail == nullptr)
1205 10 : psTail = psHead;
1206 25 : oMapLinkedDatasets[osKey] = psLinkedDataset;
1207 25 : }
1208 :
1209 : /************************************************************************/
1210 : /* OpenAndInsertNewDataset() */
1211 : /************************************************************************/
1212 :
1213 : GDALDataset *
1214 9 : PLMosaicDataset::OpenAndInsertNewDataset(const CPLString &osTmpFilename,
1215 : const CPLString &osTilename)
1216 : {
1217 9 : const char *const apszAllowedDrivers[2] = {"GTiff", nullptr};
1218 9 : GDALDataset *poDS = GDALDataset::FromHandle(
1219 : GDALOpenEx(osTmpFilename, GDAL_OF_RASTER | GDAL_OF_INTERNAL,
1220 : apszAllowedDrivers, nullptr, nullptr));
1221 9 : if (poDS != nullptr)
1222 : {
1223 7 : if (poDS->GetRasterXSize() != nQuadSize ||
1224 7 : poDS->GetRasterYSize() != nQuadSize || poDS->GetRasterCount() != 4)
1225 : {
1226 0 : CPLError(CE_Failure, CPLE_AppDefined,
1227 : "Inconsistent metatile characteristics");
1228 0 : GDALClose(poDS);
1229 0 : poDS = nullptr;
1230 : }
1231 : }
1232 : else
1233 : {
1234 2 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid GTiff dataset: %s",
1235 : osTilename.c_str());
1236 : }
1237 :
1238 9 : InsertNewDataset(osTilename, poDS);
1239 9 : return poDS;
1240 : }
1241 :
1242 : /************************************************************************/
1243 : /* GetMetaTile() */
1244 : /************************************************************************/
1245 :
1246 62 : GDALDataset *PLMosaicDataset::GetMetaTile(int tile_x, int tile_y)
1247 : {
1248 124 : const CPLString osTilename = formatTileName(tile_x, tile_y);
1249 : std::map<CPLString, PLLinkedDataset *>::const_iterator it =
1250 62 : oMapLinkedDatasets.find(osTilename);
1251 62 : if (it == oMapLinkedDatasets.end())
1252 : {
1253 50 : CPLString osTmpFilename;
1254 :
1255 50 : const CPLString osMosaicPath(GetMosaicCachePath());
1256 : osTmpFilename =
1257 25 : CPLFormFilenameSafe(osMosaicPath,
1258 : CPLSPrintf("%s_%s.tif", osMosaic.c_str(),
1259 : CPLGetFilename(osTilename)),
1260 25 : nullptr);
1261 : VSIStatBufL sStatBuf;
1262 :
1263 50 : CPLString osURL = osQuadsURL;
1264 25 : osURL += osTilename;
1265 25 : osURL += "/full";
1266 :
1267 25 : if (!osCachePathRoot.empty() && VSIStatL(osTmpFilename, &sStatBuf) == 0)
1268 : {
1269 3 : if (bTrustCache)
1270 : {
1271 1 : return OpenAndInsertNewDataset(osTmpFilename, osTilename);
1272 : }
1273 :
1274 2 : CPLDebug("PLMOSAIC",
1275 : "File %s exists. Checking if it is up-to-date...",
1276 : osTmpFilename.c_str());
1277 : // Currently we only check by file size, which should be good enough
1278 : // as the metatiles are compressed, so a change in content is likely
1279 : // to cause a change in filesize. Use of a signature would be better
1280 : // though if available in the metadata
1281 : VSIStatBufL sRemoteTileStatBuf;
1282 2 : char *pszEscapedURL = CPLEscapeString(
1283 4 : (osURL + "?api_key=" + osAPIKey).c_str(), -1, CPLES_URL);
1284 2 : CPLString osVSICURLUrl(STARTS_WITH(osURL, "/vsimem/")
1285 4 : ? osURL
1286 : : "/vsicurl?use_head=no&url=" +
1287 6 : CPLString(pszEscapedURL));
1288 2 : CPLFree(pszEscapedURL);
1289 2 : if (VSIStatL(osVSICURLUrl, &sRemoteTileStatBuf) == 0 &&
1290 0 : sRemoteTileStatBuf.st_size == sStatBuf.st_size)
1291 : {
1292 0 : CPLDebug("PLMOSAIC", "Cached tile is up-to-date");
1293 0 : return OpenAndInsertNewDataset(osTmpFilename, osTilename);
1294 : }
1295 : else
1296 : {
1297 2 : CPLDebug("PLMOSAIC", "Cached tile is not up-to-date");
1298 2 : VSIUnlink(osTmpFilename);
1299 : }
1300 : }
1301 :
1302 : // Fetch the GeoTIFF now
1303 :
1304 24 : CPLHTTPResult *psResult = Download(osURL, TRUE);
1305 24 : if (psResult == nullptr)
1306 : {
1307 16 : InsertNewDataset(osTilename, nullptr);
1308 16 : return nullptr;
1309 : }
1310 :
1311 8 : CreateMosaicCachePathIfNecessary();
1312 :
1313 8 : bool bUnlink = false;
1314 : VSILFILE *fp =
1315 8 : osCachePathRoot.size() ? VSIFOpenL(osTmpFilename, "wb") : nullptr;
1316 8 : if (fp)
1317 : {
1318 6 : VSIFWriteL(psResult->pabyData, 1, psResult->nDataLen, fp);
1319 6 : VSIFCloseL(fp);
1320 : }
1321 : else
1322 : {
1323 : // In case there's no temporary path or it is not writable
1324 : // use a in-memory dataset, and limit the cache to only one
1325 2 : if (!osCachePathRoot.empty() && nCacheMaxSize > 1)
1326 : {
1327 1 : CPLError(CE_Failure, CPLE_AppDefined,
1328 : "Cannot write into %s. Using /vsimem and reduce cache "
1329 : "to 1 entry",
1330 : osCachePathRoot.c_str());
1331 1 : FlushDatasetsCache();
1332 1 : nCacheMaxSize = 1;
1333 : }
1334 2 : bUnlink = true;
1335 : osTmpFilename = VSIMemGenerateHiddenFilename(
1336 : CPLSPrintf("single_tile_plmosaic_cache_%s_%d_%d.tif",
1337 2 : osMosaic.c_str(), tile_x, tile_y));
1338 2 : fp = VSIFOpenL(osTmpFilename, "wb");
1339 2 : if (fp)
1340 : {
1341 2 : VSIFWriteL(psResult->pabyData, 1, psResult->nDataLen, fp);
1342 2 : VSIFCloseL(fp);
1343 : }
1344 : }
1345 8 : CPLHTTPDestroyResult(psResult);
1346 8 : GDALDataset *poDS = OpenAndInsertNewDataset(osTmpFilename, osTilename);
1347 :
1348 8 : if (bUnlink)
1349 2 : VSIUnlink(osTilename);
1350 :
1351 8 : return poDS;
1352 : }
1353 :
1354 : // Move link to head of MRU list
1355 37 : PLLinkedDataset *psLinkedDataset = it->second;
1356 37 : GDALDataset *poDS = psLinkedDataset->poDS;
1357 37 : if (psLinkedDataset != psHead)
1358 : {
1359 18 : if (psLinkedDataset == psTail)
1360 2 : psTail = psLinkedDataset->psPrev;
1361 18 : if (psLinkedDataset->psPrev)
1362 18 : psLinkedDataset->psPrev->psNext = psLinkedDataset->psNext;
1363 18 : if (psLinkedDataset->psNext)
1364 16 : psLinkedDataset->psNext->psPrev = psLinkedDataset->psPrev;
1365 18 : psLinkedDataset->psNext = psHead;
1366 18 : psLinkedDataset->psPrev = nullptr;
1367 18 : psHead->psPrev = psLinkedDataset;
1368 18 : psHead = psLinkedDataset;
1369 : }
1370 :
1371 37 : return poDS;
1372 : }
1373 :
1374 : /************************************************************************/
1375 : /* GetLocationInfo() */
1376 : /************************************************************************/
1377 :
1378 4 : const char *PLMosaicDataset::GetLocationInfo(int nPixel, int nLine)
1379 : {
1380 : int nBlockXSize, nBlockYSize;
1381 4 : GetRasterBand(1)->GetBlockSize(&nBlockXSize, &nBlockYSize);
1382 :
1383 4 : const int nBlockXOff = nPixel / nBlockXSize;
1384 4 : const int nBlockYOff = nLine / nBlockYSize;
1385 4 : const int bottom_yblock =
1386 4 : (nRasterYSize - nBlockYOff * nBlockYSize) / nBlockYSize - 1;
1387 :
1388 4 : const int meta_tile_x =
1389 4 : nMetaTileXShift + (nBlockXOff * nBlockXSize) / nQuadSize;
1390 4 : const int meta_tile_y =
1391 4 : nMetaTileYShift + (bottom_yblock * nBlockYSize) / nQuadSize;
1392 :
1393 8 : CPLString osQuadURL = osQuadsURL;
1394 8 : CPLString osTilename = formatTileName(meta_tile_x, meta_tile_y);
1395 4 : osQuadURL += osTilename;
1396 :
1397 4 : if (meta_tile_x != nLastMetaTileX || meta_tile_y != nLastMetaTileY)
1398 : {
1399 3 : const CPLString osQuadScenesURL = osQuadURL + "/items";
1400 :
1401 3 : json_object_put(poLastItemsInformation);
1402 3 : poLastItemsInformation = RunRequest(osQuadScenesURL, TRUE);
1403 :
1404 3 : nLastMetaTileX = meta_tile_x;
1405 3 : nLastMetaTileY = meta_tile_y;
1406 : }
1407 :
1408 4 : osLastRetGetLocationInfo.clear();
1409 :
1410 4 : CPLXMLNode *psRoot = CPLCreateXMLNode(nullptr, CXT_Element, "LocationInfo");
1411 :
1412 4 : if (poLastItemsInformation)
1413 : {
1414 : json_object *poItems =
1415 2 : CPL_json_object_object_get(poLastItemsInformation, "items");
1416 4 : if (poItems && json_object_get_type(poItems) == json_type_array &&
1417 2 : json_object_array_length(poItems) != 0)
1418 : {
1419 : CPLXMLNode *psScenes =
1420 2 : CPLCreateXMLNode(psRoot, CXT_Element, "Scenes");
1421 2 : const auto nItemsLength = json_object_array_length(poItems);
1422 4 : for (auto i = decltype(nItemsLength){0}; i < nItemsLength; i++)
1423 : {
1424 2 : json_object *poObj = json_object_array_get_idx(poItems, i);
1425 2 : if (poObj && json_object_get_type(poObj) == json_type_object)
1426 : {
1427 : json_object *poLink =
1428 2 : CPL_json_object_object_get(poObj, "link");
1429 2 : if (poLink)
1430 : {
1431 : CPLXMLNode *psScene =
1432 2 : CPLCreateXMLNode(psScenes, CXT_Element, "Scene");
1433 : CPLXMLNode *psItem =
1434 2 : CPLCreateXMLNode(psScene, CXT_Element, "link");
1435 2 : CPLCreateXMLNode(psItem, CXT_Text,
1436 : json_object_get_string(poLink));
1437 : }
1438 : }
1439 : }
1440 : }
1441 : }
1442 :
1443 4 : char *pszXML = CPLSerializeXMLTree(psRoot);
1444 4 : CPLDestroyXMLNode(psRoot);
1445 4 : osLastRetGetLocationInfo = pszXML;
1446 4 : CPLFree(pszXML);
1447 :
1448 8 : return osLastRetGetLocationInfo.c_str();
1449 : }
1450 :
1451 : /************************************************************************/
1452 : /* IRasterIO() */
1453 : /************************************************************************/
1454 :
1455 6 : CPLErr PLMosaicDataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
1456 : int nXSize, int nYSize, void *pData,
1457 : int nBufXSize, int nBufYSize,
1458 : GDALDataType eBufType, int nBandCount,
1459 : BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
1460 : GSpacing nLineSpace, GSpacing nBandSpace,
1461 : GDALRasterIOExtraArg *psExtraArg)
1462 : {
1463 6 : if (bUseTMSForMain && !apoTMSDS.empty())
1464 1 : return apoTMSDS[0]->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
1465 : pData, nBufXSize, nBufYSize, eBufType,
1466 : nBandCount, panBandMap, nPixelSpace,
1467 1 : nLineSpace, nBandSpace, psExtraArg);
1468 :
1469 5 : return BlockBasedRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
1470 : nBufXSize, nBufYSize, eBufType, nBandCount,
1471 : panBandMap, nPixelSpace, nLineSpace, nBandSpace,
1472 5 : psExtraArg);
1473 : }
1474 :
1475 : /************************************************************************/
1476 : /* GDALRegister_PLMOSAIC() */
1477 : /************************************************************************/
1478 :
1479 2024 : void GDALRegister_PLMOSAIC()
1480 :
1481 : {
1482 2024 : if (GDALGetDriverByName("PLMOSAIC") != nullptr)
1483 283 : return;
1484 :
1485 1741 : GDALDriver *poDriver = new GDALDriver();
1486 :
1487 1741 : poDriver->SetDescription("PLMOSAIC");
1488 1741 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1489 1741 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Planet Labs Mosaics API");
1490 1741 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC,
1491 1741 : "drivers/raster/plmosaic.html");
1492 :
1493 1741 : poDriver->SetMetadataItem(GDAL_DMD_CONNECTION_PREFIX, "PLMOSAIC:");
1494 :
1495 1741 : poDriver->SetMetadataItem(
1496 : GDAL_DMD_OPENOPTIONLIST,
1497 : "<OpenOptionList>"
1498 : " <Option name='API_KEY' type='string' description='Account API key' "
1499 : "required='true'/>"
1500 : " <Option name='MOSAIC' type='string' description='Mosaic name'/>"
1501 : " <Option name='CACHE_PATH' type='string' description='Directory "
1502 : "where to put cached quads'/>"
1503 : " <Option name='TRUST_CACHE' type='boolean' description='Whether "
1504 : "already cached quads should be trusted as the most recent version' "
1505 : "default='NO'/>"
1506 : " <Option name='USE_TILES' type='boolean' description='Whether to use "
1507 : "the tile API even for full resolution data (only for Byte mosaics)' "
1508 : "default='NO'/>"
1509 1741 : "</OpenOptionList>");
1510 :
1511 1741 : poDriver->pfnIdentify = PLMosaicDataset::Identify;
1512 1741 : poDriver->pfnOpen = PLMosaicDataset::Open;
1513 :
1514 1741 : GetGDALDriverManager()->RegisterDriver(poDriver);
1515 : }
|