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