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