Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: GDAL
4 : * Purpose: STACTA (Spatio-Temporal Asset Catalog Tiled Assets) driver
5 : * Author: Even Rouault, <even dot rouault at spatialys.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2020, Even Rouault <even dot rouault at spatialys.com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_json.h"
14 : #include "cpl_mem_cache.h"
15 : #include "cpl_string.h"
16 : #include "gdal_pam.h"
17 : #include "gdal_utils.h"
18 : #include "memdataset.h"
19 : #include "tilematrixset.hpp"
20 : #include "stactadataset.h"
21 :
22 : #include <algorithm>
23 : #include <array>
24 : #include <limits>
25 : #include <map>
26 : #include <memory>
27 : #include <vector>
28 :
29 : extern "C" void GDALRegister_STACTA();
30 :
31 : // Implements a driver for
32 : // https://github.com/stac-extensions/tiled-assets
33 :
34 : /************************************************************************/
35 : /* GetAllowedDrivers() */
36 : /************************************************************************/
37 :
38 31 : static CPLStringList GetAllowedDrivers()
39 : {
40 31 : CPLStringList aosAllowedDrivers;
41 31 : aosAllowedDrivers.AddString("GTiff");
42 31 : aosAllowedDrivers.AddString("PNG");
43 31 : aosAllowedDrivers.AddString("JPEG");
44 31 : aosAllowedDrivers.AddString("JPEGXL");
45 31 : aosAllowedDrivers.AddString("WEBP");
46 31 : aosAllowedDrivers.AddString("JP2KAK");
47 31 : aosAllowedDrivers.AddString("JP2ECW");
48 31 : aosAllowedDrivers.AddString("JP2MrSID");
49 31 : aosAllowedDrivers.AddString("JP2OpenJPEG");
50 31 : return aosAllowedDrivers;
51 : }
52 :
53 : /************************************************************************/
54 : /* STACTARasterBand() */
55 : /************************************************************************/
56 :
57 65 : STACTARasterBand::STACTARasterBand(STACTADataset *poDSIn, int nBandIn,
58 65 : GDALRasterBand *poProtoBand)
59 65 : : m_eColorInterp(poProtoBand->GetColorInterpretation())
60 : {
61 65 : poDS = poDSIn;
62 65 : nBand = nBandIn;
63 65 : eDataType = poProtoBand->GetRasterDataType();
64 65 : poProtoBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
65 65 : nRasterXSize = poDSIn->GetRasterXSize();
66 65 : nRasterYSize = poDSIn->GetRasterYSize();
67 65 : m_dfNoData = poProtoBand->GetNoDataValue(&m_bHasNoDataValue);
68 65 : }
69 :
70 : /************************************************************************/
71 : /* IReadBlock() */
72 : /************************************************************************/
73 :
74 0 : CPLErr STACTARasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
75 : void *pImage)
76 : {
77 0 : auto poGDS = cpl::down_cast<STACTADataset *>(poDS);
78 0 : return poGDS->m_poDS->GetRasterBand(nBand)->ReadBlock(nBlockXOff,
79 0 : nBlockYOff, pImage);
80 : }
81 :
82 : /************************************************************************/
83 : /* IRasterIO() */
84 : /************************************************************************/
85 :
86 6 : CPLErr STACTARasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
87 : int nXSize, int nYSize, void *pData,
88 : int nBufXSize, int nBufYSize,
89 : GDALDataType eBufType, GSpacing nPixelSpace,
90 : GSpacing nLineSpace,
91 : GDALRasterIOExtraArg *psExtraArg)
92 : {
93 6 : auto poGDS = cpl::down_cast<STACTADataset *>(poDS);
94 6 : if ((nBufXSize < nXSize || nBufYSize < nYSize) &&
95 12 : poGDS->m_apoOverviewDS.size() >= 1 && eRWFlag == GF_Read)
96 : {
97 : int bTried;
98 1 : CPLErr eErr = TryOverviewRasterIO(
99 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
100 : eBufType, nPixelSpace, nLineSpace, psExtraArg, &bTried);
101 1 : if (bTried)
102 1 : return eErr;
103 : }
104 :
105 5 : return poGDS->m_poDS->GetRasterBand(nBand)->RasterIO(
106 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
107 5 : eBufType, nPixelSpace, nLineSpace, psExtraArg);
108 : }
109 :
110 : /************************************************************************/
111 : /* IRasterIO() */
112 : /************************************************************************/
113 :
114 12 : CPLErr STACTADataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
115 : int nXSize, int nYSize, void *pData,
116 : int nBufXSize, int nBufYSize,
117 : GDALDataType eBufType, int nBandCount,
118 : BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
119 : GSpacing nLineSpace, GSpacing nBandSpace,
120 : GDALRasterIOExtraArg *psExtraArg)
121 : {
122 12 : if ((nBufXSize < nXSize || nBufYSize < nYSize) &&
123 24 : m_apoOverviewDS.size() >= 1 && eRWFlag == GF_Read)
124 : {
125 : int bTried;
126 6 : CPLErr eErr = TryOverviewRasterIO(
127 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
128 : eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace,
129 : nBandSpace, psExtraArg, &bTried);
130 6 : if (bTried)
131 3 : return eErr;
132 : }
133 :
134 9 : return m_poDS->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
135 : nBufXSize, nBufYSize, eBufType, nBandCount,
136 : panBandMap, nPixelSpace, nLineSpace, nBandSpace,
137 9 : psExtraArg);
138 : }
139 :
140 : /************************************************************************/
141 : /* GetOverviewCount() */
142 : /************************************************************************/
143 :
144 34 : int STACTARasterBand::GetOverviewCount()
145 : {
146 34 : STACTADataset *poGDS = cpl::down_cast<STACTADataset *>(poDS);
147 34 : return static_cast<int>(poGDS->m_apoOverviewDS.size());
148 : }
149 :
150 : /************************************************************************/
151 : /* GetOverview() */
152 : /************************************************************************/
153 :
154 24 : GDALRasterBand *STACTARasterBand::GetOverview(int nIdx)
155 : {
156 24 : STACTADataset *poGDS = cpl::down_cast<STACTADataset *>(poDS);
157 24 : if (nIdx < 0 || nIdx >= GetOverviewCount())
158 0 : return nullptr;
159 24 : return poGDS->m_apoOverviewDS[nIdx]->GetRasterBand(nBand);
160 : }
161 :
162 : /************************************************************************/
163 : /* GetNoDataValue() */
164 : /************************************************************************/
165 :
166 20 : double STACTARasterBand::GetNoDataValue(int *pbHasNoData)
167 : {
168 20 : if (pbHasNoData)
169 20 : *pbHasNoData = m_bHasNoDataValue;
170 20 : return m_dfNoData;
171 : }
172 :
173 : /************************************************************************/
174 : /* STACTARawRasterBand() */
175 : /************************************************************************/
176 :
177 99 : STACTARawRasterBand::STACTARawRasterBand(STACTARawDataset *poDSIn, int nBandIn,
178 99 : GDALRasterBand *poProtoBand)
179 99 : : m_eColorInterp(poProtoBand->GetColorInterpretation())
180 : {
181 99 : poDS = poDSIn;
182 99 : nBand = nBandIn;
183 99 : eDataType = poProtoBand->GetRasterDataType();
184 99 : nBlockXSize = 256;
185 99 : nBlockYSize = 256;
186 : int nProtoBlockXSize;
187 : int nProtoBlockYSize;
188 : // Use tile block size if it divides the metatile dimension.
189 99 : poProtoBand->GetBlockSize(&nProtoBlockXSize, &nProtoBlockYSize);
190 99 : if ((poDSIn->m_nMetaTileWidth % nProtoBlockXSize) == 0 &&
191 99 : (poDSIn->m_nMetaTileHeight % nProtoBlockYSize) == 0)
192 : {
193 99 : nBlockXSize = nProtoBlockXSize;
194 99 : nBlockYSize = nProtoBlockYSize;
195 : }
196 99 : nRasterXSize = poDSIn->GetRasterXSize();
197 99 : nRasterYSize = poDSIn->GetRasterYSize();
198 99 : m_dfNoData = poProtoBand->GetNoDataValue(&m_bHasNoDataValue);
199 99 : }
200 :
201 : /************************************************************************/
202 : /* STACTARawRasterBand() */
203 : /************************************************************************/
204 :
205 92 : STACTARawRasterBand::STACTARawRasterBand(STACTARawDataset *poDSIn, int nBandIn,
206 : GDALDataType eDT, bool bSetNoData,
207 92 : double dfNoData)
208 : {
209 92 : poDS = poDSIn;
210 92 : nBand = nBandIn;
211 92 : eDataType = eDT;
212 92 : nBlockXSize = 256;
213 92 : nBlockYSize = 256;
214 92 : nRasterXSize = poDSIn->GetRasterXSize();
215 92 : nRasterYSize = poDSIn->GetRasterYSize();
216 92 : m_bHasNoDataValue = bSetNoData;
217 92 : m_dfNoData = dfNoData;
218 92 : }
219 :
220 : /************************************************************************/
221 : /* GetNoDataValue() */
222 : /************************************************************************/
223 :
224 95 : double STACTARawRasterBand::GetNoDataValue(int *pbHasNoData)
225 : {
226 95 : if (pbHasNoData)
227 89 : *pbHasNoData = m_bHasNoDataValue;
228 95 : return m_dfNoData;
229 : }
230 :
231 : /************************************************************************/
232 : /* IReadBlock() */
233 : /************************************************************************/
234 :
235 0 : CPLErr STACTARawRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
236 : void *pImage)
237 : {
238 0 : const int nXOff = nBlockXOff * nBlockXSize;
239 0 : const int nYOff = nBlockYOff * nBlockYSize;
240 0 : const int nXSize = std::min(nBlockXSize, nRasterXSize - nXOff);
241 0 : const int nYSize = std::min(nBlockYSize, nRasterYSize - nYOff);
242 : GDALRasterIOExtraArg sExtraArgs;
243 0 : INIT_RASTERIO_EXTRA_ARG(sExtraArgs);
244 0 : const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
245 0 : return IRasterIO(GF_Read, nXOff, nYOff, nXSize, nYSize, pImage, nBlockXSize,
246 : nBlockYSize, eDataType, nDTSize,
247 0 : static_cast<GSpacing>(nDTSize) * nBlockXSize, &sExtraArgs);
248 : }
249 :
250 : /************************************************************************/
251 : /* IRasterIO() */
252 : /************************************************************************/
253 :
254 6 : CPLErr STACTARawRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
255 : int nXSize, int nYSize, void *pData,
256 : int nBufXSize, int nBufYSize,
257 : GDALDataType eBufType,
258 : GSpacing nPixelSpace, GSpacing nLineSpace,
259 : GDALRasterIOExtraArg *psExtraArg)
260 : {
261 6 : CPLDebugOnly("STACTA", "Band %d RasterIO: %d,%d,%d,%d->%d,%d", nBand, nXOff,
262 : nYOff, nXSize, nYSize, nBufXSize, nBufYSize);
263 6 : auto poGDS = cpl::down_cast<STACTARawDataset *>(poDS);
264 :
265 6 : const int nKernelRadius = 3; // up to 3 for Lanczos
266 : const int nRadiusX =
267 6 : nKernelRadius * static_cast<int>(std::ceil(nXSize / nBufXSize));
268 : const int nRadiusY =
269 6 : nKernelRadius * static_cast<int>(std::ceil(nYSize / nBufYSize));
270 6 : const int nXOffMod = std::max(0, nXOff - nRadiusX);
271 6 : const int nYOffMod = std::max(0, nYOff - nRadiusY);
272 6 : const int nXSizeMod = static_cast<int>(std::min(
273 12 : nXOff + nXSize + static_cast<GIntBig>(nRadiusX),
274 6 : static_cast<GIntBig>(nRasterXSize))) -
275 6 : nXOffMod;
276 6 : const int nYSizeMod = static_cast<int>(std::min(
277 12 : nYOff + nYSize + static_cast<GIntBig>(nRadiusY),
278 6 : static_cast<GIntBig>(nRasterYSize))) -
279 6 : nYOffMod;
280 :
281 6 : const bool bRequestFitsInSingleMetaTile =
282 6 : nXOffMod / poGDS->m_nMetaTileWidth ==
283 9 : (nXOffMod + nXSizeMod - 1) / poGDS->m_nMetaTileWidth &&
284 3 : nYOffMod / poGDS->m_nMetaTileHeight ==
285 3 : (nYOffMod + nYSizeMod - 1) / poGDS->m_nMetaTileHeight;
286 :
287 6 : if (eRWFlag != GF_Read || ((nXSize != nBufXSize || nYSize != nBufYSize) &&
288 1 : !bRequestFitsInSingleMetaTile))
289 : {
290 0 : if (!(eRWFlag == GF_Read && nXSizeMod <= 4096 && nYSizeMod <= 4096))
291 : {
292 : // If not reading at nominal resolution, fallback to default block
293 : // reading
294 0 : return GDALRasterBand::IRasterIO(
295 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize,
296 0 : nBufYSize, eBufType, nPixelSpace, nLineSpace, psExtraArg);
297 : }
298 : }
299 :
300 : // Use optimized dataset level RasterIO()
301 6 : return poGDS->IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
302 : nBufXSize, nBufYSize, eBufType, 1, &nBand,
303 6 : nPixelSpace, nLineSpace, 0, psExtraArg);
304 : }
305 :
306 : /************************************************************************/
307 : /* DoVSICLOUDSubstitution() */
308 : /************************************************************************/
309 :
310 0 : static std::string DoVSICLOUDSubstitution(const std::string &osFilename)
311 : {
312 0 : std::string ret;
313 0 : constexpr const char *HTTPS_PROTOCOL = "https://";
314 0 : if (cpl::starts_with(osFilename, HTTPS_PROTOCOL))
315 : {
316 0 : constexpr const char *AZURE_BLOB = ".blob.core.windows.net/";
317 0 : constexpr const char *AWS = ".amazonaws.com/";
318 0 : constexpr const char *GOOGLE_CLOUD_STORAGE =
319 : "https://storage.googleapis.com/";
320 : size_t nPos;
321 0 : if ((nPos = osFilename.find(AZURE_BLOB)) != std::string::npos)
322 : {
323 0 : ret = "/vsiaz/" + osFilename.substr(nPos + strlen(AZURE_BLOB));
324 : }
325 0 : else if ((nPos = osFilename.find(AWS)) != std::string::npos)
326 : {
327 0 : constexpr const char *DOT_S3_DOT = ".s3.";
328 0 : const auto nPos2 = osFilename.find(DOT_S3_DOT);
329 0 : if (nPos2 != std::string::npos)
330 : {
331 0 : ret = "/vsis3/" +
332 0 : osFilename.substr(strlen(HTTPS_PROTOCOL),
333 0 : nPos2 - strlen(HTTPS_PROTOCOL)) +
334 0 : "/" + osFilename.substr(nPos + strlen(AWS));
335 : }
336 : }
337 0 : else if (cpl::starts_with(osFilename, GOOGLE_CLOUD_STORAGE))
338 : {
339 0 : ret = "/vsigs/" + osFilename.substr(strlen(GOOGLE_CLOUD_STORAGE));
340 : }
341 : }
342 0 : return ret;
343 : }
344 :
345 : /************************************************************************/
346 : /* IRasterIO() */
347 : /************************************************************************/
348 :
349 19 : CPLErr STACTARawDataset::IRasterIO(
350 : GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
351 : void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
352 : int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
353 : GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
354 : {
355 19 : CPLDebugOnly("STACTA", "Dataset RasterIO: %d,%d,%d,%d->%d,%d", nXOff, nYOff,
356 : nXSize, nYSize, nBufXSize, nBufYSize);
357 19 : const int nMinBlockX = nXOff / m_nMetaTileWidth;
358 19 : const int nMaxBlockX = (nXOff + nXSize - 1) / m_nMetaTileWidth;
359 19 : const int nMinBlockY = nYOff / m_nMetaTileHeight;
360 19 : const int nMaxBlockY = (nYOff + nYSize - 1) / m_nMetaTileHeight;
361 :
362 19 : const int nKernelRadius = 3; // up to 3 for Lanczos
363 : const int nRadiusX =
364 19 : nKernelRadius * static_cast<int>(std::ceil(nXSize / nBufXSize));
365 : const int nRadiusY =
366 19 : nKernelRadius * static_cast<int>(std::ceil(nYSize / nBufYSize));
367 19 : const int nXOffMod = std::max(0, nXOff - nRadiusX);
368 19 : const int nYOffMod = std::max(0, nYOff - nRadiusY);
369 19 : const int nXSizeMod = static_cast<int>(std::min(
370 38 : nXOff + nXSize + static_cast<GIntBig>(nRadiusX),
371 19 : static_cast<GIntBig>(nRasterXSize))) -
372 19 : nXOffMod;
373 19 : const int nYSizeMod = static_cast<int>(std::min(
374 38 : nYOff + nYSize + static_cast<GIntBig>(nRadiusY),
375 19 : static_cast<GIntBig>(nRasterYSize))) -
376 19 : nYOffMod;
377 :
378 19 : const bool bRequestFitsInSingleMetaTile =
379 19 : nXOffMod / m_nMetaTileWidth ==
380 27 : (nXOffMod + nXSizeMod - 1) / m_nMetaTileWidth &&
381 8 : nYOffMod / m_nMetaTileHeight ==
382 8 : (nYOffMod + nYSizeMod - 1) / m_nMetaTileHeight;
383 19 : const auto eBandDT = GetRasterBand(1)->GetRasterDataType();
384 19 : const int nDTSize = GDALGetDataTypeSizeBytes(eBandDT);
385 :
386 19 : if (eRWFlag != GF_Read || ((nXSize != nBufXSize || nYSize != nBufYSize) &&
387 7 : !bRequestFitsInSingleMetaTile))
388 : {
389 1 : if (eRWFlag == GF_Read && nXSizeMod <= 4096 && nYSizeMod <= 4096 &&
390 : nBandCount <= 10)
391 : {
392 : // If extracting from a small enough window, do a RasterIO()
393 : // at full resolution into a MEM dataset, and then proceeding to
394 : // resampling on it. This will avoid to fallback on block based
395 : // approach.
396 : GDALRasterIOExtraArg sExtraArgs;
397 1 : INIT_RASTERIO_EXTRA_ARG(sExtraArgs);
398 1 : const size_t nXSizeModeMulYSizeModMulDTSize =
399 1 : static_cast<size_t>(nXSizeMod) * nYSizeMod * nDTSize;
400 : std::vector<GByte> abyBuf(nXSizeModeMulYSizeModMulDTSize *
401 2 : nBandCount);
402 1 : if (IRasterIO(GF_Read, nXOffMod, nYOffMod, nXSizeMod, nYSizeMod,
403 1 : &abyBuf[0], nXSizeMod, nYSizeMod, eBandDT, nBandCount,
404 : panBandMap, nDTSize,
405 1 : static_cast<GSpacing>(nDTSize) * nXSizeMod,
406 1 : static_cast<GSpacing>(nDTSize) * nXSizeMod *
407 1 : nYSizeMod,
408 1 : &sExtraArgs) != CE_None)
409 : {
410 0 : return CE_Failure;
411 : }
412 :
413 : auto poMEMDS = std::unique_ptr<MEMDataset>(MEMDataset::Create(
414 2 : "", nXSizeMod, nYSizeMod, 0, eBandDT, nullptr));
415 4 : for (int i = 0; i < nBandCount; i++)
416 : {
417 3 : auto hBand = MEMCreateRasterBandEx(
418 3 : poMEMDS.get(), i + 1,
419 3 : &abyBuf[0] + i * nXSizeModeMulYSizeModMulDTSize, eBandDT, 0,
420 : 0, false);
421 3 : poMEMDS->AddMEMBand(hBand);
422 : }
423 :
424 1 : sExtraArgs.eResampleAlg = psExtraArg->eResampleAlg;
425 1 : if (psExtraArg->bFloatingPointWindowValidity)
426 : {
427 0 : sExtraArgs.bFloatingPointWindowValidity = true;
428 0 : sExtraArgs.dfXOff = psExtraArg->dfXOff - nXOffMod;
429 0 : sExtraArgs.dfYOff = psExtraArg->dfYOff - nYOffMod;
430 0 : sExtraArgs.dfXSize = psExtraArg->dfXSize;
431 0 : sExtraArgs.dfYSize = psExtraArg->dfYSize;
432 : }
433 1 : return poMEMDS->RasterIO(
434 : GF_Read, nXOff - nXOffMod, nYOff - nYOffMod, nXSize, nYSize,
435 : pData, nBufXSize, nBufYSize, eBufType, nBandCount, nullptr,
436 1 : nPixelSpace, nLineSpace, nBandSpace, &sExtraArgs);
437 : }
438 :
439 : // If not reading at nominal resolution, fallback to default block
440 : // reading
441 0 : return GDALDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
442 : pData, nBufXSize, nBufYSize, eBufType,
443 : nBandCount, panBandMap, nPixelSpace,
444 0 : nLineSpace, nBandSpace, psExtraArg);
445 : }
446 :
447 18 : int nBufYOff = 0;
448 :
449 : // If the (uncompressed) size of a metatile is small enough, then download
450 : // it entirely to minimize the number of network requests
451 18 : const bool bDownloadWholeMetaTile =
452 35 : m_poMasterDS->m_bDownloadWholeMetaTile ||
453 17 : (static_cast<GIntBig>(m_nMetaTileWidth) * m_nMetaTileHeight * nBands *
454 17 : nDTSize <
455 : 128 * 1024);
456 :
457 : // Split the request on each metatile that it intersects
458 33 : for (int iY = nMinBlockY; iY <= nMaxBlockY; iY++)
459 : {
460 18 : const int nTileYOff = std::max(0, nYOff - iY * m_nMetaTileHeight);
461 : const int nTileYSize =
462 18 : std::min((iY + 1) * m_nMetaTileHeight, nYOff + nYSize) -
463 18 : std::max(nYOff, iY * m_nMetaTileHeight);
464 :
465 18 : int nBufXOff = 0;
466 43 : for (int iX = nMinBlockX; iX <= nMaxBlockX; iX++)
467 : {
468 28 : CPLString osURL(m_osURLTemplate);
469 : osURL.replaceAll("{TileRow}",
470 28 : CPLSPrintf("%d", iY + m_nMinMetaTileRow));
471 : osURL.replaceAll("{TileCol}",
472 28 : CPLSPrintf("%d", iX + m_nMinMetaTileCol));
473 28 : if (m_poMasterDS->m_bVSICLOUDSubstitutionOK)
474 0 : osURL = DoVSICLOUDSubstitution(osURL);
475 :
476 28 : const int nTileXOff = std::max(0, nXOff - iX * m_nMetaTileWidth);
477 : const int nTileXSize =
478 28 : std::min((iX + 1) * m_nMetaTileWidth, nXOff + nXSize) -
479 28 : std::max(nXOff, iX * m_nMetaTileWidth);
480 :
481 28 : const int nBufXSizeEffective =
482 28 : bRequestFitsInSingleMetaTile ? nBufXSize : nTileXSize;
483 28 : const int nBufYSizeEffective =
484 28 : bRequestFitsInSingleMetaTile ? nBufYSize : nTileYSize;
485 :
486 28 : bool bMissingTile = false;
487 : do
488 : {
489 : std::unique_ptr<GDALDataset> *ppoTileDS =
490 28 : m_poMasterDS->m_oCacheTileDS.getPtr(osURL);
491 28 : if (ppoTileDS == nullptr)
492 : {
493 :
494 : // Avoid probing side car files
495 : CPLConfigOptionSetter oSetter(
496 : "GDAL_DISABLE_READDIR_ON_OPEN", "EMPTY_DIR",
497 18 : /* bSetOnlyIfUndefined = */ true);
498 :
499 18 : CPLStringList aosAllowedDrivers(GetAllowedDrivers());
500 0 : std::unique_ptr<GDALDataset> poTileDS;
501 18 : if (bDownloadWholeMetaTile && !VSIIsLocal(osURL.c_str()))
502 : {
503 0 : if (m_poMasterDS->m_bSkipMissingMetaTile)
504 0 : CPLPushErrorHandler(CPLQuietErrorHandler);
505 0 : VSILFILE *fp = VSIFOpenL(osURL, "rb");
506 0 : if (m_poMasterDS->m_bSkipMissingMetaTile)
507 0 : CPLPopErrorHandler();
508 0 : if (fp == nullptr)
509 : {
510 0 : if (!m_poMasterDS->m_bTriedVSICLOUDSubstitution &&
511 0 : cpl::starts_with(osURL, "https://"))
512 : {
513 0 : m_poMasterDS->m_bTriedVSICLOUDSubstitution =
514 : true;
515 : std::string osNewURL =
516 0 : DoVSICLOUDSubstitution(osURL);
517 0 : if (!osNewURL.empty())
518 : {
519 0 : CPLDebug("STACTA", "Retrying with %s",
520 : osNewURL.c_str());
521 0 : if (m_poMasterDS->m_bSkipMissingMetaTile)
522 0 : CPLPushErrorHandler(
523 : CPLQuietErrorHandler);
524 0 : fp = VSIFOpenL(osNewURL.c_str(), "rb");
525 0 : if (m_poMasterDS->m_bSkipMissingMetaTile)
526 0 : CPLPopErrorHandler();
527 0 : if (fp != nullptr)
528 : {
529 0 : VSIFCloseL(fp);
530 0 : m_poMasterDS
531 0 : ->m_bVSICLOUDSubstitutionOK = true;
532 0 : osURL = std::move(osNewURL);
533 0 : break;
534 : }
535 : }
536 : }
537 : }
538 0 : if (fp == nullptr)
539 : {
540 0 : if (m_poMasterDS->m_bSkipMissingMetaTile)
541 : {
542 0 : m_poMasterDS->m_oCacheTileDS.insert(osURL,
543 0 : nullptr);
544 0 : bMissingTile = true;
545 0 : break;
546 : }
547 0 : CPLError(CE_Failure, CPLE_OpenFailed,
548 : "Cannot open %s", osURL.c_str());
549 0 : return CE_Failure;
550 : }
551 0 : GByte *pabyBuf = nullptr;
552 0 : vsi_l_offset nSize = 0;
553 0 : if (!VSIIngestFile(fp, nullptr, &pabyBuf, &nSize, -1))
554 : {
555 0 : VSIFCloseL(fp);
556 0 : return CE_Failure;
557 : }
558 0 : VSIFCloseL(fp);
559 : const CPLString osMEMFilename(
560 : VSIMemGenerateHiddenFilename(
561 0 : std::string("stacta_")
562 0 : .append(CPLString(osURL)
563 0 : .replaceAll("/", "_")
564 0 : .replaceAll("\\", "_"))
565 0 : .c_str()));
566 0 : VSIFCloseL(VSIFileFromMemBuffer(osMEMFilename, pabyBuf,
567 : nSize, TRUE));
568 0 : poTileDS = std::unique_ptr<GDALDataset>(
569 : GDALDataset::Open(osMEMFilename,
570 : GDAL_OF_INTERNAL | GDAL_OF_RASTER,
571 0 : aosAllowedDrivers.List()));
572 0 : if (poTileDS)
573 0 : poTileDS->MarkSuppressOnClose();
574 : else
575 0 : VSIUnlink(osMEMFilename);
576 : }
577 33 : else if (bDownloadWholeMetaTile ||
578 15 : (!STARTS_WITH(osURL, "http://") &&
579 13 : !STARTS_WITH(osURL, "https://")))
580 : {
581 16 : aosAllowedDrivers.AddString("HTTP");
582 16 : if (m_poMasterDS->m_bSkipMissingMetaTile)
583 4 : CPLPushErrorHandler(CPLQuietErrorHandler);
584 : poTileDS =
585 32 : std::unique_ptr<GDALDataset>(GDALDataset::Open(
586 : osURL, GDAL_OF_INTERNAL | GDAL_OF_RASTER,
587 32 : aosAllowedDrivers.List()));
588 16 : if (m_poMasterDS->m_bSkipMissingMetaTile)
589 4 : CPLPopErrorHandler();
590 : }
591 : else
592 : {
593 2 : if (m_poMasterDS->m_bSkipMissingMetaTile)
594 0 : CPLPushErrorHandler(CPLQuietErrorHandler);
595 4 : poTileDS = std::unique_ptr<GDALDataset>(
596 4 : GDALDataset::Open(("/vsicurl/" + osURL).c_str(),
597 : GDAL_OF_INTERNAL | GDAL_OF_RASTER,
598 4 : aosAllowedDrivers.List()));
599 2 : if (m_poMasterDS->m_bSkipMissingMetaTile)
600 0 : CPLPopErrorHandler();
601 2 : if (poTileDS == nullptr)
602 : {
603 0 : if (!m_poMasterDS->m_bTriedVSICLOUDSubstitution &&
604 0 : cpl::starts_with(osURL, "https://"))
605 : {
606 0 : m_poMasterDS->m_bTriedVSICLOUDSubstitution =
607 : true;
608 : std::string osNewURL =
609 0 : DoVSICLOUDSubstitution(osURL);
610 0 : if (!osNewURL.empty())
611 : {
612 0 : CPLDebug("STACTA", "Retrying with %s",
613 : osNewURL.c_str());
614 0 : if (m_poMasterDS->m_bSkipMissingMetaTile)
615 0 : CPLPushErrorHandler(
616 : CPLQuietErrorHandler);
617 0 : poTileDS = std::unique_ptr<GDALDataset>(
618 : GDALDataset::Open(
619 : osNewURL.c_str(),
620 : GDAL_OF_INTERNAL | GDAL_OF_RASTER,
621 0 : aosAllowedDrivers.List()));
622 0 : if (m_poMasterDS->m_bSkipMissingMetaTile)
623 0 : CPLPopErrorHandler();
624 0 : if (poTileDS)
625 : {
626 0 : m_poMasterDS
627 0 : ->m_bVSICLOUDSubstitutionOK = true;
628 0 : osURL = std::move(osNewURL);
629 : m_osURLTemplate =
630 0 : DoVSICLOUDSubstitution(
631 0 : m_osURLTemplate);
632 0 : break;
633 : }
634 : }
635 : }
636 : }
637 : }
638 18 : if (poTileDS == nullptr)
639 : {
640 5 : if (m_poMasterDS->m_bSkipMissingMetaTile)
641 : {
642 2 : m_poMasterDS->m_oCacheTileDS.insert(
643 2 : osURL, std::move(poTileDS));
644 2 : bMissingTile = true;
645 2 : break;
646 : }
647 3 : CPLError(CE_Failure, CPLE_OpenFailed, "Cannot open %s",
648 : osURL.c_str());
649 3 : return CE_Failure;
650 : }
651 13 : ppoTileDS = &m_poMasterDS->m_oCacheTileDS.insert(
652 13 : osURL, std::move(poTileDS));
653 : }
654 23 : std::unique_ptr<GDALDataset> &poTileDS = *ppoTileDS;
655 23 : if (poTileDS == nullptr)
656 : {
657 0 : bMissingTile = true;
658 0 : break;
659 : }
660 :
661 : GDALRasterIOExtraArg sExtraArgs;
662 23 : INIT_RASTERIO_EXTRA_ARG(sExtraArgs);
663 23 : if (bRequestFitsInSingleMetaTile)
664 : {
665 6 : sExtraArgs.eResampleAlg = psExtraArg->eResampleAlg;
666 6 : if (psExtraArg->bFloatingPointWindowValidity)
667 : {
668 3 : sExtraArgs.bFloatingPointWindowValidity = true;
669 3 : sExtraArgs.dfXOff =
670 3 : psExtraArg->dfXOff - iX * m_nMetaTileWidth;
671 3 : sExtraArgs.dfYOff =
672 3 : psExtraArg->dfYOff - iY * m_nMetaTileHeight;
673 3 : sExtraArgs.dfXSize = psExtraArg->dfXSize;
674 3 : sExtraArgs.dfYSize = psExtraArg->dfYSize;
675 : }
676 : }
677 23 : CPLDebugOnly("STACTA", "Reading %d,%d,%d,%d in %s", nTileXOff,
678 : nTileYOff, nTileXSize, nTileYSize, osURL.c_str());
679 23 : if (poTileDS->RasterIO(
680 : GF_Read, nTileXOff, nTileYOff, nTileXSize, nTileYSize,
681 23 : static_cast<GByte *>(pData) + nBufXOff * nPixelSpace +
682 23 : nBufYOff * nLineSpace,
683 : nBufXSizeEffective, nBufYSizeEffective, eBufType,
684 : nBandCount, panBandMap, nPixelSpace, nLineSpace,
685 23 : nBandSpace, &sExtraArgs) != CE_None)
686 : {
687 0 : return CE_Failure;
688 : }
689 : } while (false);
690 :
691 25 : if (bMissingTile)
692 : {
693 2 : CPLDebugOnly("STACTA", "Missing metatile %s", osURL.c_str());
694 8 : for (int iBand = 0; iBand < nBandCount; iBand++)
695 : {
696 6 : int bHasNoData = FALSE;
697 6 : double dfNodata = GetRasterBand(panBandMap[iBand])
698 6 : ->GetNoDataValue(&bHasNoData);
699 6 : if (!bHasNoData)
700 0 : dfNodata = 0;
701 6150 : for (int nYBufOff = 0; nYBufOff < nBufYSizeEffective;
702 : nYBufOff++)
703 : {
704 6144 : GByte *pabyDest = static_cast<GByte *>(pData) +
705 6144 : iBand * nBandSpace +
706 6144 : nBufXOff * nPixelSpace +
707 6144 : (nBufYOff + nYBufOff) * nLineSpace;
708 6144 : GDALCopyWords(&dfNodata, GDT_Float64, 0, pabyDest,
709 : eBufType, static_cast<int>(nPixelSpace),
710 : nBufXSizeEffective);
711 : }
712 : }
713 : }
714 :
715 25 : if (iX == nMinBlockX)
716 : {
717 32 : nBufXOff = m_nMetaTileWidth -
718 16 : std::max(0, nXOff - nMinBlockX * m_nMetaTileWidth);
719 : }
720 : else
721 : {
722 9 : nBufXOff += m_nMetaTileWidth;
723 : }
724 : }
725 :
726 15 : if (iY == nMinBlockY)
727 : {
728 30 : nBufYOff = m_nMetaTileHeight -
729 15 : std::max(0, nYOff - nMinBlockY * m_nMetaTileHeight);
730 : }
731 : else
732 : {
733 0 : nBufYOff += m_nMetaTileHeight;
734 : }
735 : }
736 :
737 15 : return CE_None;
738 : }
739 :
740 : /************************************************************************/
741 : /* GetGeoTransform() */
742 : /************************************************************************/
743 :
744 4 : CPLErr STACTARawDataset::GetGeoTransform(GDALGeoTransform >) const
745 : {
746 4 : gt = m_gt;
747 4 : return CE_None;
748 : }
749 :
750 : /************************************************************************/
751 : /* Identify() */
752 : /************************************************************************/
753 :
754 57226 : int STACTADataset::Identify(GDALOpenInfo *poOpenInfo)
755 : {
756 57226 : if (STARTS_WITH(poOpenInfo->pszFilename, "STACTA:"))
757 : {
758 6 : return true;
759 : }
760 :
761 57220 : const bool bIsSingleDriver = poOpenInfo->IsSingleAllowedDriver("STACTA");
762 57218 : if (bIsSingleDriver && (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
763 4 : STARTS_WITH(poOpenInfo->pszFilename, "https://")))
764 : {
765 1 : return true;
766 : }
767 :
768 57217 : if (
769 : #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
770 57762 : (!bIsSingleDriver && !poOpenInfo->IsExtensionEqualToCI("json")) ||
771 : #endif
772 545 : poOpenInfo->nHeaderBytes == 0)
773 : {
774 57036 : return false;
775 : }
776 :
777 440 : for (int i = 0; i < 2; i++)
778 : {
779 : // TryToIngest() may reallocate pabyHeader, so do not move this
780 : // before the loop.
781 310 : const char *pszHeader =
782 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
783 310 : while (*pszHeader != 0 &&
784 310 : std::isspace(static_cast<unsigned char>(*pszHeader)))
785 0 : ++pszHeader;
786 310 : if (bIsSingleDriver)
787 : {
788 4 : return pszHeader[0] == '{';
789 : }
790 :
791 306 : if (strstr(pszHeader, "\"stac_extensions\"") != nullptr &&
792 74 : (strstr(pszHeader, "\"tiled-assets\"") != nullptr ||
793 56 : strstr(
794 : pszHeader,
795 : "https:\\/\\/stac-extensions.github.io\\/tiled-assets\\/") !=
796 56 : nullptr ||
797 56 : strstr(pszHeader,
798 : "https://stac-extensions.github.io/tiled-assets/") !=
799 : nullptr))
800 : {
801 46 : return true;
802 : }
803 :
804 260 : if (i == 0)
805 : {
806 : // Should be enough for a STACTA .json file
807 130 : poOpenInfo->TryToIngest(32768);
808 : }
809 : }
810 :
811 130 : return false;
812 : }
813 :
814 : /************************************************************************/
815 : /* Open() */
816 : /************************************************************************/
817 :
818 25 : bool STACTADataset::Open(GDALOpenInfo *poOpenInfo)
819 : {
820 50 : CPLString osFilename(poOpenInfo->pszFilename);
821 50 : CPLString osAssetName;
822 50 : CPLString osTMS;
823 25 : if (STARTS_WITH(poOpenInfo->pszFilename, "STACTA:"))
824 : {
825 : const CPLStringList aosTokens(CSLTokenizeString2(
826 3 : poOpenInfo->pszFilename, ":", CSLT_HONOURSTRINGS));
827 4 : if (aosTokens.size() != 2 && aosTokens.size() != 3 &&
828 1 : aosTokens.size() != 4)
829 0 : return false;
830 3 : osFilename = aosTokens[1];
831 3 : if (aosTokens.size() >= 3)
832 3 : osAssetName = aosTokens[2];
833 3 : if (aosTokens.size() == 4)
834 1 : osTMS = aosTokens[3];
835 : }
836 :
837 50 : CPLJSONDocument oDoc;
838 50 : if (STARTS_WITH(osFilename, "http://") ||
839 25 : STARTS_WITH(osFilename, "https://"))
840 : {
841 0 : if (!oDoc.LoadUrl(osFilename, nullptr))
842 0 : return false;
843 : }
844 : else
845 : {
846 25 : if (!oDoc.Load(osFilename))
847 0 : return false;
848 : }
849 50 : const auto oRoot = oDoc.GetRoot();
850 75 : const auto oProperties = oRoot["properties"];
851 50 : if (!oProperties.IsValid() ||
852 25 : oProperties.GetType() != CPLJSONObject::Type::Object)
853 : {
854 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing properties");
855 0 : return false;
856 : }
857 :
858 75 : const auto oAssetTemplates = oRoot["asset_templates"];
859 50 : if (!oAssetTemplates.IsValid() ||
860 25 : oAssetTemplates.GetType() != CPLJSONObject::Type::Object)
861 : {
862 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing asset_templates");
863 0 : return false;
864 : }
865 :
866 50 : const auto aoAssetTemplates = oAssetTemplates.GetChildren();
867 25 : if (aoAssetTemplates.size() == 0)
868 : {
869 0 : CPLError(CE_Failure, CPLE_AppDefined, "Empty asset_templates");
870 0 : return false;
871 : }
872 :
873 75 : const auto oTMSs = oProperties.GetObj("tiles:tile_matrix_sets");
874 25 : if (!oTMSs.IsValid() || oTMSs.GetType() != CPLJSONObject::Type::Object)
875 : {
876 0 : CPLError(CE_Failure, CPLE_AppDefined,
877 : "Missing properties[\"tiles:tile_matrix_sets\"]");
878 0 : return false;
879 : }
880 50 : const auto aoTMSs = oTMSs.GetChildren();
881 25 : if (aoTMSs.empty())
882 : {
883 0 : CPLError(CE_Failure, CPLE_AppDefined,
884 : "Empty properties[\"tiles:tile_matrix_sets\"]");
885 0 : return false;
886 : }
887 :
888 52 : if ((aoAssetTemplates.size() >= 2 || aoTMSs.size() >= 2) &&
889 52 : osAssetName.empty() && osTMS.empty())
890 : {
891 2 : int nSDSCount = 0;
892 5 : for (const auto &oAssetTemplate : aoAssetTemplates)
893 : {
894 6 : const CPLString osAssetNameSubDS = oAssetTemplate.GetName();
895 3 : const char *pszAssetNameSubDS = osAssetNameSubDS.c_str();
896 3 : if (aoTMSs.size() >= 2)
897 : {
898 3 : for (const auto &oTMS : aoTMSs)
899 : {
900 2 : const CPLString osTMSSubDS = oTMS.GetName();
901 2 : const char *pszTMSSubDS = osTMSSubDS.c_str();
902 2 : GDALDataset::SetMetadataItem(
903 : CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
904 : CPLSPrintf("STACTA:\"%s\":%s:%s", osFilename.c_str(),
905 : pszAssetNameSubDS, pszTMSSubDS),
906 : "SUBDATASETS");
907 2 : GDALDataset::SetMetadataItem(
908 : CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
909 : CPLSPrintf("Asset %s, tile matrix set %s",
910 : pszAssetNameSubDS, pszTMSSubDS),
911 : "SUBDATASETS");
912 2 : nSDSCount++;
913 : }
914 : }
915 : else
916 : {
917 2 : GDALDataset::SetMetadataItem(
918 : CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
919 : CPLSPrintf("STACTA:\"%s\":%s", osFilename.c_str(),
920 : pszAssetNameSubDS),
921 : "SUBDATASETS");
922 2 : GDALDataset::SetMetadataItem(
923 : CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
924 : CPLSPrintf("Asset %s", pszAssetNameSubDS), "SUBDATASETS");
925 2 : nSDSCount++;
926 : }
927 : }
928 2 : return true;
929 : }
930 :
931 23 : if (osAssetName.empty())
932 : {
933 20 : osAssetName = aoAssetTemplates[0].GetName();
934 : }
935 46 : const auto oAssetTemplate = oAssetTemplates.GetObj(osAssetName);
936 46 : if (!oAssetTemplate.IsValid() ||
937 23 : oAssetTemplate.GetType() != CPLJSONObject::Type::Object)
938 : {
939 0 : CPLError(CE_Failure, CPLE_AppDefined,
940 : "Cannot find asset_templates[\"%s\"]", osAssetName.c_str());
941 0 : return false;
942 : }
943 :
944 23 : if (osTMS.empty())
945 : {
946 22 : osTMS = aoTMSs[0].GetName();
947 : }
948 46 : const auto oTMS = oTMSs.GetObj(osTMS);
949 23 : if (!oTMS.IsValid() || oTMS.GetType() != CPLJSONObject::Type::Object)
950 : {
951 0 : CPLError(CE_Failure, CPLE_AppDefined,
952 : "Cannot find properties[\"tiles:tile_matrix_sets\"][\"%s\"]",
953 : osTMS.c_str());
954 0 : return false;
955 : }
956 :
957 : auto poTMS = gdal::TileMatrixSet::parse(
958 46 : oTMS.Format(CPLJSONObject::PrettyFormat::Plain).c_str());
959 23 : if (poTMS == nullptr)
960 0 : return false;
961 :
962 69 : CPLString osURLTemplate = oAssetTemplate.GetString("href");
963 23 : if (osURLTemplate.empty())
964 : {
965 0 : CPLError(CE_Failure, CPLE_AppDefined,
966 : "Cannot find asset_templates[\"%s\"][\"href\"]",
967 : osAssetName.c_str());
968 : }
969 23 : osURLTemplate.replaceAll("{TileMatrixSet}", osTMS);
970 :
971 : // UPDATE oMapVSIToURIPrefix in apps/gdalalg_raster_tile if updating below
972 : const std::map<std::string, std::string> oMapURIPrefixToVSI = {
973 : {"s3", "/vsis3/"},
974 : {"gs", "/vsigs/"},
975 : {"az", "/vsiaz/"}, // Not universally recognized
976 : {"azure", "/vsiaz/"}, // Not universally recognized
977 161 : };
978 :
979 23 : if (cpl::starts_with(osURLTemplate, "file://"))
980 : {
981 0 : osURLTemplate = osURLTemplate.substr(strlen("file://"));
982 : }
983 : else
984 : {
985 23 : const auto nPosColonSlashSlash = osURLTemplate.find("://");
986 23 : if (nPosColonSlashSlash != std::string::npos)
987 : {
988 : const auto oIter = oMapURIPrefixToVSI.find(
989 3 : osURLTemplate.substr(0, nPosColonSlashSlash));
990 3 : if (oIter != oMapURIPrefixToVSI.end())
991 : {
992 0 : osURLTemplate = std::string(oIter->second)
993 0 : .append(osURLTemplate.substr(
994 0 : nPosColonSlashSlash + strlen("://")));
995 : }
996 : }
997 : }
998 :
999 43 : if (!cpl::starts_with(osURLTemplate, "http://") &&
1000 20 : !cpl::starts_with(osURLTemplate, "https://"))
1001 : {
1002 20 : if (STARTS_WITH(osURLTemplate, "./"))
1003 20 : osURLTemplate = osURLTemplate.substr(2);
1004 40 : osURLTemplate = CPLProjectRelativeFilenameSafe(
1005 40 : CPLGetDirnameSafe(osFilename).c_str(), osURLTemplate);
1006 : }
1007 :
1008 : // Parse optional tile matrix set limits
1009 46 : std::map<CPLString, Limits> oMapLimits;
1010 69 : const auto oTMLinks = oProperties.GetObj("tiles:tile_matrix_links");
1011 23 : if (oTMLinks.IsValid())
1012 : {
1013 20 : if (oTMLinks.GetType() != CPLJSONObject::Type::Object)
1014 : {
1015 0 : CPLError(
1016 : CE_Failure, CPLE_AppDefined,
1017 : "Invalid type for properties[\"tiles:tile_matrix_links\"]");
1018 0 : return false;
1019 : }
1020 :
1021 60 : auto oLimits = oTMLinks[osTMS]["limits"];
1022 40 : if (oLimits.IsValid() &&
1023 20 : oLimits.GetType() == CPLJSONObject::Type::Object)
1024 : {
1025 76 : for (const auto &oLimit : oLimits.GetChildren())
1026 : {
1027 56 : Limits limits;
1028 56 : limits.min_tile_col = oLimit.GetInteger("min_tile_col");
1029 56 : limits.max_tile_col = oLimit.GetInteger("max_tile_col");
1030 56 : limits.min_tile_row = oLimit.GetInteger("min_tile_row");
1031 56 : limits.max_tile_row = oLimit.GetInteger("max_tile_row");
1032 56 : oMapLimits[oLimit.GetName()] = limits;
1033 : }
1034 : }
1035 : }
1036 23 : const auto &tmsList = poTMS->tileMatrixList();
1037 23 : if (tmsList.empty())
1038 0 : return false;
1039 :
1040 46 : m_bSkipMissingMetaTile = CPLTestBool(CSLFetchNameValueDef(
1041 23 : poOpenInfo->papszOpenOptions, "SKIP_MISSING_METATILE",
1042 : CPLGetConfigOption("GDAL_STACTA_SKIP_MISSING_METATILE", "NO")));
1043 :
1044 : // STAC 1.1 uses bands instead of eo:bands and raster:bands
1045 69 : const auto oBands = oAssetTemplate.GetArray("bands");
1046 :
1047 : // Check if there are both eo:bands and raster:bands extension
1048 : // If so, we don't need to fetch a prototype metatile to derive the
1049 : // information we need (number of bands, data type and nodata value)
1050 : const auto oEoBands =
1051 66 : oBands.IsValid() ? oBands : oAssetTemplate.GetArray("eo:bands");
1052 : const auto oRasterBands =
1053 66 : oBands.IsValid() ? oBands : oAssetTemplate.GetArray("raster:bands");
1054 :
1055 46 : std::vector<GDALDataType> aeDT;
1056 46 : std::vector<double> adfNoData;
1057 46 : std::vector<bool> abSetNoData;
1058 23 : int nExpectedBandCount = 0;
1059 23 : if (oRasterBands.IsValid())
1060 : {
1061 11 : if (oEoBands.IsValid() && oEoBands.Size() != oRasterBands.Size())
1062 : {
1063 1 : CPLError(CE_Warning, CPLE_AppDefined,
1064 : "Number of bands in eo:bands and raster:bands is not "
1065 : "identical. Ignoring the later");
1066 : }
1067 : else
1068 : {
1069 10 : nExpectedBandCount = oRasterBands.Size();
1070 :
1071 : const struct
1072 : {
1073 : const char *pszStacDataType;
1074 : GDALDataType eGDALDataType;
1075 10 : } aDataTypeMapping[] = {
1076 : {"int8", GDT_Int8},
1077 : {"int16", GDT_Int16},
1078 : {"int32", GDT_Int32},
1079 : {"int64", GDT_Int64},
1080 : {"uint8", GDT_Byte},
1081 : {"uint16", GDT_UInt16},
1082 : {"uint32", GDT_UInt32},
1083 : {"uint64", GDT_UInt64},
1084 : // float16: 16-bit float; unhandled
1085 : {"float32", GDT_Float32},
1086 : {"float64", GDT_Float64},
1087 : {"cint16", GDT_CInt16},
1088 : {"cint32", GDT_CInt32},
1089 : {"cfloat32", GDT_CFloat32},
1090 : {"cfloat64", GDT_CFloat64},
1091 : };
1092 :
1093 42 : for (int i = 0; i < nExpectedBandCount; ++i)
1094 : {
1095 35 : if (oRasterBands[i].GetType() != CPLJSONObject::Type::Object)
1096 : {
1097 1 : CPLError(CE_Failure, CPLE_AppDefined,
1098 : "Wrong raster:bands[%d]", i);
1099 3 : return false;
1100 : }
1101 : const std::string osDataType =
1102 68 : oRasterBands[i].GetString("data_type");
1103 34 : GDALDataType eDT = GDT_Unknown;
1104 290 : for (const auto &oTuple : aDataTypeMapping)
1105 : {
1106 288 : if (osDataType == oTuple.pszStacDataType)
1107 : {
1108 32 : eDT = oTuple.eGDALDataType;
1109 32 : break;
1110 : }
1111 : }
1112 34 : if (eDT == GDT_Unknown)
1113 : {
1114 2 : CPLError(CE_Failure, CPLE_AppDefined,
1115 : "Wrong raster:bands[%d].data_type = %s", i,
1116 : osDataType.c_str());
1117 2 : return false;
1118 : }
1119 32 : aeDT.push_back(eDT);
1120 :
1121 96 : const auto oNoData = oRasterBands[i].GetObj("nodata");
1122 32 : if (oNoData.GetType() == CPLJSONObject::Type::String)
1123 : {
1124 48 : const std::string osNoData = oNoData.ToString();
1125 16 : if (osNoData == "inf")
1126 : {
1127 5 : abSetNoData.push_back(true);
1128 5 : adfNoData.push_back(
1129 5 : std::numeric_limits<double>::infinity());
1130 : }
1131 11 : else if (osNoData == "-inf")
1132 : {
1133 5 : abSetNoData.push_back(true);
1134 5 : adfNoData.push_back(
1135 5 : -std::numeric_limits<double>::infinity());
1136 : }
1137 6 : else if (osNoData == "nan")
1138 : {
1139 5 : abSetNoData.push_back(true);
1140 5 : adfNoData.push_back(
1141 5 : std::numeric_limits<double>::quiet_NaN());
1142 : }
1143 : else
1144 : {
1145 1 : CPLError(CE_Warning, CPLE_AppDefined,
1146 : "Invalid raster:bands[%d].nodata = %s", i,
1147 : osNoData.c_str());
1148 1 : abSetNoData.push_back(false);
1149 1 : adfNoData.push_back(
1150 1 : std::numeric_limits<double>::quiet_NaN());
1151 : }
1152 : }
1153 16 : else if (oNoData.GetType() == CPLJSONObject::Type::Integer ||
1154 29 : oNoData.GetType() == CPLJSONObject::Type::Long ||
1155 13 : oNoData.GetType() == CPLJSONObject::Type::Double)
1156 : {
1157 8 : abSetNoData.push_back(true);
1158 8 : adfNoData.push_back(oNoData.ToDouble());
1159 : }
1160 8 : else if (!oNoData.IsValid())
1161 : {
1162 7 : abSetNoData.push_back(false);
1163 7 : adfNoData.push_back(
1164 7 : std::numeric_limits<double>::quiet_NaN());
1165 : }
1166 : else
1167 : {
1168 1 : CPLError(CE_Warning, CPLE_AppDefined,
1169 : "Invalid raster:bands[%d].nodata", i);
1170 1 : abSetNoData.push_back(false);
1171 1 : adfNoData.push_back(
1172 1 : std::numeric_limits<double>::quiet_NaN());
1173 : }
1174 : }
1175 :
1176 7 : CPLAssert(aeDT.size() == abSetNoData.size());
1177 7 : CPLAssert(adfNoData.size() == abSetNoData.size());
1178 : }
1179 : }
1180 :
1181 20 : std::unique_ptr<GDALDataset> poProtoDS;
1182 20 : if (aeDT.empty())
1183 : {
1184 13 : for (int i = 0; i < static_cast<int>(tmsList.size()); i++)
1185 : {
1186 : // Open a metatile to get mostly its band data type
1187 13 : int nProtoTileCol = 0;
1188 13 : int nProtoTileRow = 0;
1189 13 : auto oIterLimit = oMapLimits.find(tmsList[i].mId);
1190 13 : if (oIterLimit != oMapLimits.end())
1191 : {
1192 10 : nProtoTileCol = oIterLimit->second.min_tile_col;
1193 10 : nProtoTileRow = oIterLimit->second.min_tile_row;
1194 : }
1195 : const CPLString osURL =
1196 13 : CPLString(osURLTemplate)
1197 26 : .replaceAll("{TileMatrix}", tmsList[i].mId)
1198 26 : .replaceAll("{TileRow}", CPLSPrintf("%d", nProtoTileRow))
1199 26 : .replaceAll("{TileCol}", CPLSPrintf("%d", nProtoTileCol));
1200 38 : CPLString osProtoDSName = (STARTS_WITH(osURL, "http://") ||
1201 12 : STARTS_WITH(osURL, "https://"))
1202 26 : ? CPLString("/vsicurl/" + osURL)
1203 26 : : osURL;
1204 : CPLConfigOptionSetter oSetter("GDAL_DISABLE_READDIR_ON_OPEN",
1205 : "EMPTY_DIR",
1206 13 : /* bSetOnlyIfUndefined = */ true);
1207 13 : if (m_bSkipMissingMetaTile)
1208 2 : CPLPushErrorHandler(CPLQuietErrorHandler);
1209 13 : poProtoDS.reset(GDALDataset::Open(osProtoDSName.c_str(),
1210 : GDAL_OF_RASTER,
1211 26 : GetAllowedDrivers().List()));
1212 13 : if (m_bSkipMissingMetaTile)
1213 2 : CPLPopErrorHandler();
1214 13 : if (poProtoDS != nullptr)
1215 : {
1216 11 : break;
1217 : }
1218 :
1219 4 : if (!m_bTriedVSICLOUDSubstitution &&
1220 2 : cpl::starts_with(osURL, "https://"))
1221 : {
1222 0 : m_bTriedVSICLOUDSubstitution = true;
1223 0 : std::string osNewURL = DoVSICLOUDSubstitution(osURL);
1224 0 : if (!osNewURL.empty())
1225 : {
1226 0 : CPLDebug("STACTA", "Retrying with %s", osNewURL.c_str());
1227 0 : if (m_bSkipMissingMetaTile)
1228 0 : CPLPushErrorHandler(CPLQuietErrorHandler);
1229 0 : poProtoDS.reset(
1230 : GDALDataset::Open(osNewURL.c_str(), GDAL_OF_RASTER,
1231 0 : GetAllowedDrivers().List()));
1232 0 : if (m_bSkipMissingMetaTile)
1233 0 : CPLPopErrorHandler();
1234 0 : if (poProtoDS != nullptr)
1235 : {
1236 0 : osURLTemplate = DoVSICLOUDSubstitution(osURLTemplate);
1237 0 : break;
1238 : }
1239 : }
1240 : }
1241 :
1242 2 : if (!m_bSkipMissingMetaTile)
1243 : {
1244 2 : CPLError(CE_Failure, CPLE_OpenFailed, "Cannot open %s",
1245 : osURL.c_str());
1246 2 : return false;
1247 : }
1248 : }
1249 11 : if (poProtoDS == nullptr)
1250 : {
1251 0 : if (m_bSkipMissingMetaTile)
1252 : {
1253 0 : CPLError(CE_Failure, CPLE_AppDefined,
1254 : "Cannot find prototype dataset");
1255 0 : return false;
1256 : }
1257 : }
1258 : else
1259 : {
1260 11 : nExpectedBandCount = poProtoDS->GetRasterCount();
1261 : }
1262 : }
1263 :
1264 : // Iterate over tile matrices to create corresponding STACTARawDataset
1265 : // objects
1266 68 : for (int i = static_cast<int>(tmsList.size() - 1); i >= 0; i--)
1267 : {
1268 50 : const auto &oTM = tmsList[i];
1269 50 : int nMatrixWidth = oTM.mMatrixWidth;
1270 50 : int nMatrixHeight = oTM.mMatrixHeight;
1271 50 : auto oIterLimit = oMapLimits.find(tmsList[i].mId);
1272 50 : if (oIterLimit != oMapLimits.end())
1273 : {
1274 41 : nMatrixWidth = oIterLimit->second.max_tile_col -
1275 41 : oIterLimit->second.min_tile_col + 1;
1276 41 : nMatrixHeight = oIterLimit->second.max_tile_row -
1277 41 : oIterLimit->second.min_tile_row + 1;
1278 : }
1279 50 : if (nMatrixWidth <= 0 || oTM.mTileWidth > INT_MAX / nMatrixWidth ||
1280 50 : nMatrixHeight <= 0 || oTM.mTileHeight > INT_MAX / nMatrixHeight)
1281 : {
1282 0 : continue;
1283 : }
1284 50 : auto poRawDS = std::make_unique<STACTARawDataset>();
1285 100 : if (!poRawDS->InitRaster(poProtoDS.get(), aeDT, abSetNoData, adfNoData,
1286 50 : poTMS.get(), tmsList[i].mId, oTM, oMapLimits))
1287 : {
1288 0 : return false;
1289 : }
1290 50 : poRawDS->m_osURLTemplate = osURLTemplate;
1291 50 : poRawDS->m_osURLTemplate.replaceAll("{TileMatrix}", tmsList[i].mId);
1292 50 : poRawDS->m_poMasterDS = this;
1293 :
1294 50 : if (m_poDS == nullptr)
1295 : {
1296 18 : nRasterXSize = poRawDS->GetRasterXSize();
1297 18 : nRasterYSize = poRawDS->GetRasterYSize();
1298 18 : m_oSRS = poRawDS->m_oSRS;
1299 18 : m_gt = poRawDS->m_gt;
1300 18 : m_poDS = std::move(poRawDS);
1301 : }
1302 : else
1303 : {
1304 32 : const double dfMinX = m_gt[0];
1305 32 : const double dfMaxX = m_gt[0] + GetRasterXSize() * m_gt[1];
1306 32 : const double dfMaxY = m_gt[3];
1307 32 : const double dfMinY = m_gt[3] + GetRasterYSize() * m_gt[5];
1308 :
1309 32 : const double dfOvrMinX = poRawDS->m_gt[0];
1310 : const double dfOvrMaxX =
1311 32 : poRawDS->m_gt[0] + poRawDS->GetRasterXSize() * poRawDS->m_gt[1];
1312 32 : const double dfOvrMaxY = poRawDS->m_gt[3];
1313 : const double dfOvrMinY =
1314 32 : poRawDS->m_gt[3] + poRawDS->GetRasterYSize() * poRawDS->m_gt[5];
1315 :
1316 32 : if (fabs(dfMinX - dfOvrMinX) < 1e-10 * fabs(dfMinX) &&
1317 30 : fabs(dfMinY - dfOvrMinY) < 1e-10 * fabs(dfMinY) &&
1318 30 : fabs(dfMaxX - dfOvrMaxX) < 1e-10 * fabs(dfMaxX) &&
1319 30 : fabs(dfMaxY - dfOvrMaxY) < 1e-10 * fabs(dfMaxY))
1320 : {
1321 30 : m_apoOverviewDS.emplace_back(std::move(poRawDS));
1322 : }
1323 : else
1324 : {
1325 : // If this zoom level doesn't share the same origin and extent
1326 : // as the most resoluted one, then subset it
1327 2 : CPLStringList aosOptions;
1328 2 : aosOptions.AddString("-of");
1329 2 : aosOptions.AddString("VRT");
1330 2 : aosOptions.AddString("-projwin");
1331 2 : aosOptions.AddString(CPLSPrintf("%.17g", dfMinX));
1332 2 : aosOptions.AddString(CPLSPrintf("%.17g", dfMaxY));
1333 2 : aosOptions.AddString(CPLSPrintf("%.17g", dfMaxX));
1334 2 : aosOptions.AddString(CPLSPrintf("%.17g", dfMinY));
1335 : auto psOptions =
1336 2 : GDALTranslateOptionsNew(aosOptions.List(), nullptr);
1337 : auto hDS =
1338 2 : GDALTranslate("", GDALDataset::ToHandle(poRawDS.get()),
1339 : psOptions, nullptr);
1340 2 : GDALTranslateOptionsFree(psOptions);
1341 2 : if (hDS == nullptr)
1342 0 : continue;
1343 2 : m_apoIntermediaryDS.emplace_back(std::move(poRawDS));
1344 2 : m_apoOverviewDS.emplace_back(GDALDataset::FromHandle(hDS));
1345 : }
1346 : }
1347 : }
1348 18 : if (m_poDS == nullptr)
1349 : {
1350 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find valid tile matrix");
1351 0 : return false;
1352 : }
1353 :
1354 : // Create main bands
1355 83 : for (int i = 0; i < m_poDS->GetRasterCount(); i++)
1356 : {
1357 65 : auto poSrcBand = m_poDS->GetRasterBand(i + 1);
1358 65 : auto poBand = new STACTARasterBand(this, i + 1, poSrcBand);
1359 65 : if (oEoBands.IsValid() && oEoBands.Size() == nExpectedBandCount)
1360 : {
1361 : // Set band metadata
1362 50 : if (oEoBands[i].GetType() == CPLJSONObject::Type::Object)
1363 : {
1364 142 : for (const auto &oItem : oEoBands[i].GetChildren())
1365 : {
1366 92 : if (oBands.IsValid())
1367 : {
1368 : // STAC 1.1
1369 26 : if (STARTS_WITH(oItem.GetName().c_str(), "eo:"))
1370 : {
1371 2 : poBand->GDALRasterBand::SetMetadataItem(
1372 2 : oItem.GetName().c_str() + strlen("eo:"),
1373 2 : oItem.ToString().c_str());
1374 : }
1375 42 : else if (oItem.GetName() != "data_type" &&
1376 54 : oItem.GetName() != "nodata" &&
1377 48 : oItem.GetName() != "unit" &&
1378 46 : oItem.GetName() != "raster:scale" &&
1379 77 : oItem.GetName() != "raster:offset" &&
1380 34 : oItem.GetName() != "raster:bits_per_sample")
1381 : {
1382 16 : poBand->GDALRasterBand::SetMetadataItem(
1383 16 : oItem.GetName().c_str(),
1384 16 : oItem.ToString().c_str());
1385 : }
1386 : }
1387 : else
1388 : {
1389 : // STAC 1.0
1390 132 : poBand->GDALRasterBand::SetMetadataItem(
1391 198 : oItem.GetName().c_str(), oItem.ToString().c_str());
1392 : }
1393 : }
1394 : }
1395 : }
1396 227 : if (oRasterBands.IsValid() &&
1397 97 : oRasterBands.Size() == nExpectedBandCount &&
1398 97 : oRasterBands[i].GetType() == CPLJSONObject::Type::Object)
1399 : {
1400 32 : poBand->m_osUnit = oRasterBands[i].GetString("unit");
1401 64 : const double dfScale = oRasterBands[i].GetDouble(
1402 32 : oBands.IsValid() ? "raster:scale" : "scale");
1403 32 : if (dfScale != 0)
1404 5 : poBand->m_dfScale = dfScale;
1405 64 : poBand->m_dfOffset = oRasterBands[i].GetDouble(
1406 32 : oBands.IsValid() ? "raster:offset" : "offset");
1407 64 : const int nBitsPerSample = oRasterBands[i].GetInteger(
1408 32 : oBands.IsValid() ? "raster:bits_per_sample"
1409 : : "bits_per_sample");
1410 5 : if (((nBitsPerSample >= 1 && nBitsPerSample <= 7) &&
1411 37 : poBand->GetRasterDataType() == GDT_Byte) ||
1412 0 : ((nBitsPerSample >= 9 && nBitsPerSample <= 15) &&
1413 0 : poBand->GetRasterDataType() == GDT_UInt16))
1414 : {
1415 5 : poBand->GDALRasterBand::SetMetadataItem(
1416 : "NBITS", CPLSPrintf("%d", nBitsPerSample),
1417 : "IMAGE_STRUCTURE");
1418 : }
1419 : }
1420 65 : SetBand(i + 1, poBand);
1421 : }
1422 :
1423 : // Set dataset metadata
1424 127 : for (const auto &oItem : oProperties.GetChildren())
1425 : {
1426 218 : const auto osName = oItem.GetName();
1427 203 : if (osName != "tiles:tile_matrix_links" &&
1428 203 : osName != "tiles:tile_matrix_sets" &&
1429 76 : !cpl::starts_with(osName, "proj:"))
1430 : {
1431 70 : GDALDataset::SetMetadataItem(osName.c_str(),
1432 140 : oItem.ToString().c_str());
1433 : }
1434 : }
1435 :
1436 18 : if (poProtoDS)
1437 : {
1438 : const char *pszInterleave =
1439 11 : poProtoDS->GetMetadataItem("INTERLEAVE", "IMAGE_STRUCTURE");
1440 11 : GDALDataset::SetMetadataItem("INTERLEAVE",
1441 : pszInterleave ? pszInterleave : "PIXEL",
1442 : "IMAGE_STRUCTURE");
1443 : }
1444 : else
1445 : {
1446 : // A bit bold to assume that, but that should be a reasonable
1447 : // setting
1448 7 : GDALDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
1449 : }
1450 :
1451 18 : m_bDownloadWholeMetaTile = CPLTestBool(CSLFetchNameValueDef(
1452 18 : poOpenInfo->papszOpenOptions, "WHOLE_METATILE", "NO"));
1453 :
1454 18 : return true;
1455 : }
1456 :
1457 : /************************************************************************/
1458 : /* ~STACTADataset() */
1459 : /************************************************************************/
1460 :
1461 50 : STACTADataset::~STACTADataset()
1462 : {
1463 25 : m_poDS.reset();
1464 25 : m_apoOverviewDS.clear();
1465 25 : m_apoIntermediaryDS.clear();
1466 50 : }
1467 :
1468 : /************************************************************************/
1469 : /* FlushCache() */
1470 : /************************************************************************/
1471 :
1472 0 : CPLErr STACTADataset::FlushCache(bool bAtClosing)
1473 : {
1474 0 : m_oCacheTileDS.clear();
1475 0 : return GDALDataset::FlushCache(bAtClosing);
1476 : }
1477 :
1478 : /************************************************************************/
1479 : /* InitRaster() */
1480 : /************************************************************************/
1481 :
1482 50 : bool STACTARawDataset::InitRaster(GDALDataset *poProtoDS,
1483 : const std::vector<GDALDataType> &aeDT,
1484 : const std::vector<bool> &abSetNoData,
1485 : const std::vector<double> &adfNoData,
1486 : const gdal::TileMatrixSet *poTMS,
1487 : const std::string &osTMId,
1488 : const gdal::TileMatrixSet::TileMatrix &oTM,
1489 : const std::map<CPLString, Limits> &oMapLimits)
1490 : {
1491 50 : int nMatrixWidth = oTM.mMatrixWidth;
1492 50 : int nMatrixHeight = oTM.mMatrixHeight;
1493 50 : auto oIterLimit = oMapLimits.find(osTMId);
1494 50 : if (oIterLimit != oMapLimits.end())
1495 : {
1496 41 : m_nMinMetaTileCol = oIterLimit->second.min_tile_col;
1497 41 : m_nMinMetaTileRow = oIterLimit->second.min_tile_row;
1498 41 : nMatrixWidth = oIterLimit->second.max_tile_col - m_nMinMetaTileCol + 1;
1499 41 : nMatrixHeight = oIterLimit->second.max_tile_row - m_nMinMetaTileRow + 1;
1500 : }
1501 50 : m_nMetaTileWidth = oTM.mTileWidth;
1502 50 : m_nMetaTileHeight = oTM.mTileHeight;
1503 50 : nRasterXSize = nMatrixWidth * m_nMetaTileWidth;
1504 50 : nRasterYSize = nMatrixHeight * m_nMetaTileHeight;
1505 :
1506 50 : if (poProtoDS)
1507 : {
1508 132 : for (int i = 0; i < poProtoDS->GetRasterCount(); i++)
1509 : {
1510 99 : auto poProtoBand = poProtoDS->GetRasterBand(i + 1);
1511 99 : auto poBand = new STACTARawRasterBand(this, i + 1, poProtoBand);
1512 99 : SetBand(i + 1, poBand);
1513 : }
1514 : }
1515 : else
1516 : {
1517 109 : for (int i = 0; i < static_cast<int>(aeDT.size()); i++)
1518 : {
1519 92 : auto poBand = new STACTARawRasterBand(this, i + 1, aeDT[i],
1520 92 : abSetNoData[i], adfNoData[i]);
1521 92 : SetBand(i + 1, poBand);
1522 : }
1523 : }
1524 :
1525 100 : CPLString osCRS = poTMS->crs().c_str();
1526 50 : if (osCRS == "http://www.opengis.net/def/crs/OGC/1.3/CRS84")
1527 48 : osCRS = "EPSG:4326";
1528 50 : if (m_oSRS.SetFromUserInput(osCRS) != OGRERR_NONE)
1529 : {
1530 0 : return false;
1531 : }
1532 50 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1533 50 : m_gt[0] = oTM.mTopLeftX + m_nMinMetaTileCol * m_nMetaTileWidth * oTM.mResX;
1534 50 : m_gt[1] = oTM.mResX;
1535 50 : m_gt[3] = oTM.mTopLeftY - m_nMinMetaTileRow * m_nMetaTileHeight * oTM.mResY;
1536 50 : m_gt[5] = -oTM.mResY;
1537 50 : SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
1538 :
1539 50 : return true;
1540 : }
1541 :
1542 : /************************************************************************/
1543 : /* GetSpatialRef () */
1544 : /************************************************************************/
1545 :
1546 5 : const OGRSpatialReference *STACTADataset::GetSpatialRef() const
1547 : {
1548 5 : return nBands == 0 ? nullptr : &m_oSRS;
1549 : }
1550 :
1551 : /************************************************************************/
1552 : /* GetGeoTransform() */
1553 : /************************************************************************/
1554 :
1555 5 : CPLErr STACTADataset::GetGeoTransform(GDALGeoTransform >) const
1556 : {
1557 5 : gt = m_gt;
1558 5 : return nBands == 0 ? CE_Failure : CE_None;
1559 : }
1560 :
1561 : /************************************************************************/
1562 : /* OpenStatic() */
1563 : /************************************************************************/
1564 :
1565 25 : GDALDataset *STACTADataset::OpenStatic(GDALOpenInfo *poOpenInfo)
1566 : {
1567 25 : if (!Identify(poOpenInfo))
1568 0 : return nullptr;
1569 50 : auto poDS = std::make_unique<STACTADataset>();
1570 25 : if (!poDS->Open(poOpenInfo))
1571 5 : return nullptr;
1572 20 : return poDS.release();
1573 : }
1574 :
1575 : /************************************************************************/
1576 : /* GDALRegister_STACTA() */
1577 : /************************************************************************/
1578 :
1579 2024 : void GDALRegister_STACTA()
1580 :
1581 : {
1582 2024 : if (GDALGetDriverByName("STACTA") != nullptr)
1583 283 : return;
1584 :
1585 1741 : GDALDriver *poDriver = new GDALDriver();
1586 :
1587 1741 : poDriver->SetDescription("STACTA");
1588 1741 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1589 1741 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
1590 1741 : "Spatio-Temporal Asset Catalog Tiled Assets");
1591 1741 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/stacta.html");
1592 1741 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "json");
1593 1741 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1594 1741 : poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
1595 1741 : poDriver->SetMetadataItem(
1596 : GDAL_DMD_OPENOPTIONLIST,
1597 : "<OpenOptionList>"
1598 : " <Option name='WHOLE_METATILE' type='boolean' "
1599 : "description='Whether to download whole metatiles'/>"
1600 : " <Option name='SKIP_MISSING_METATILE' type='boolean' "
1601 : "description='Whether to gracefully skip missing metatiles'/>"
1602 1741 : "</OpenOptionList>");
1603 :
1604 1741 : poDriver->pfnOpen = STACTADataset::OpenStatic;
1605 1741 : poDriver->pfnIdentify = STACTADataset::Identify;
1606 :
1607 1741 : GetGDALDriverManager()->RegisterDriver(poDriver);
1608 : }
|