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 : m_poMasterDS
530 0 : ->m_bVSICLOUDSubstitutionOK = true;
531 0 : osURL = std::move(osNewURL);
532 0 : break;
533 : }
534 : }
535 : }
536 : }
537 0 : if (fp == nullptr)
538 : {
539 0 : if (m_poMasterDS->m_bSkipMissingMetaTile)
540 : {
541 0 : m_poMasterDS->m_oCacheTileDS.insert(osURL,
542 0 : nullptr);
543 0 : bMissingTile = true;
544 0 : break;
545 : }
546 0 : CPLError(CE_Failure, CPLE_OpenFailed,
547 : "Cannot open %s", osURL.c_str());
548 0 : return CE_Failure;
549 : }
550 0 : GByte *pabyBuf = nullptr;
551 0 : vsi_l_offset nSize = 0;
552 0 : if (!VSIIngestFile(fp, nullptr, &pabyBuf, &nSize, -1))
553 : {
554 0 : VSIFCloseL(fp);
555 0 : return CE_Failure;
556 : }
557 0 : VSIFCloseL(fp);
558 : const CPLString osMEMFilename(
559 : VSIMemGenerateHiddenFilename(
560 0 : std::string("stacta_")
561 0 : .append(CPLString(osURL)
562 0 : .replaceAll("/", "_")
563 0 : .replaceAll("\\", "_"))
564 0 : .c_str()));
565 0 : VSIFCloseL(VSIFileFromMemBuffer(osMEMFilename, pabyBuf,
566 : nSize, TRUE));
567 0 : poTileDS = std::unique_ptr<GDALDataset>(
568 : GDALDataset::Open(osMEMFilename,
569 : GDAL_OF_INTERNAL | GDAL_OF_RASTER,
570 0 : aosAllowedDrivers.List()));
571 0 : if (poTileDS)
572 0 : poTileDS->MarkSuppressOnClose();
573 : else
574 0 : VSIUnlink(osMEMFilename);
575 : }
576 33 : else if (bDownloadWholeMetaTile ||
577 15 : (!STARTS_WITH(osURL, "http://") &&
578 13 : !STARTS_WITH(osURL, "https://")))
579 : {
580 16 : aosAllowedDrivers.AddString("HTTP");
581 16 : if (m_poMasterDS->m_bSkipMissingMetaTile)
582 4 : CPLPushErrorHandler(CPLQuietErrorHandler);
583 : poTileDS =
584 32 : std::unique_ptr<GDALDataset>(GDALDataset::Open(
585 : osURL, GDAL_OF_INTERNAL | GDAL_OF_RASTER,
586 32 : aosAllowedDrivers.List()));
587 16 : if (m_poMasterDS->m_bSkipMissingMetaTile)
588 4 : CPLPopErrorHandler();
589 : }
590 : else
591 : {
592 2 : if (m_poMasterDS->m_bSkipMissingMetaTile)
593 0 : CPLPushErrorHandler(CPLQuietErrorHandler);
594 4 : poTileDS = std::unique_ptr<GDALDataset>(
595 4 : GDALDataset::Open(("/vsicurl/" + osURL).c_str(),
596 : GDAL_OF_INTERNAL | GDAL_OF_RASTER,
597 4 : aosAllowedDrivers.List()));
598 2 : if (m_poMasterDS->m_bSkipMissingMetaTile)
599 0 : CPLPopErrorHandler();
600 2 : if (poTileDS == nullptr)
601 : {
602 0 : if (!m_poMasterDS->m_bTriedVSICLOUDSubstitution &&
603 0 : cpl::starts_with(osURL, "https://"))
604 : {
605 0 : m_poMasterDS->m_bTriedVSICLOUDSubstitution =
606 : true;
607 : std::string osNewURL =
608 0 : DoVSICLOUDSubstitution(osURL);
609 0 : if (!osNewURL.empty())
610 : {
611 0 : CPLDebug("STACTA", "Retrying with %s",
612 : osNewURL.c_str());
613 0 : if (m_poMasterDS->m_bSkipMissingMetaTile)
614 0 : CPLPushErrorHandler(
615 : CPLQuietErrorHandler);
616 0 : poTileDS = std::unique_ptr<GDALDataset>(
617 : GDALDataset::Open(
618 : osNewURL.c_str(),
619 : GDAL_OF_INTERNAL | GDAL_OF_RASTER,
620 0 : aosAllowedDrivers.List()));
621 0 : if (m_poMasterDS->m_bSkipMissingMetaTile)
622 0 : CPLPopErrorHandler();
623 0 : if (poTileDS)
624 : {
625 0 : m_poMasterDS
626 0 : ->m_bVSICLOUDSubstitutionOK = true;
627 0 : osURL = std::move(osNewURL);
628 : m_osURLTemplate =
629 0 : DoVSICLOUDSubstitution(
630 0 : m_osURLTemplate);
631 0 : break;
632 : }
633 : }
634 : }
635 : }
636 : }
637 18 : if (poTileDS == nullptr)
638 : {
639 5 : if (m_poMasterDS->m_bSkipMissingMetaTile)
640 : {
641 2 : m_poMasterDS->m_oCacheTileDS.insert(
642 2 : osURL, std::move(poTileDS));
643 2 : bMissingTile = true;
644 2 : break;
645 : }
646 3 : CPLError(CE_Failure, CPLE_OpenFailed, "Cannot open %s",
647 : osURL.c_str());
648 3 : return CE_Failure;
649 : }
650 13 : ppoTileDS = &m_poMasterDS->m_oCacheTileDS.insert(
651 13 : osURL, std::move(poTileDS));
652 : }
653 23 : std::unique_ptr<GDALDataset> &poTileDS = *ppoTileDS;
654 23 : if (poTileDS == nullptr)
655 : {
656 0 : bMissingTile = true;
657 0 : break;
658 : }
659 :
660 : GDALRasterIOExtraArg sExtraArgs;
661 23 : INIT_RASTERIO_EXTRA_ARG(sExtraArgs);
662 23 : if (bRequestFitsInSingleMetaTile)
663 : {
664 6 : sExtraArgs.eResampleAlg = psExtraArg->eResampleAlg;
665 6 : if (psExtraArg->bFloatingPointWindowValidity)
666 : {
667 3 : sExtraArgs.bFloatingPointWindowValidity = true;
668 3 : sExtraArgs.dfXOff =
669 3 : psExtraArg->dfXOff - iX * m_nMetaTileWidth;
670 3 : sExtraArgs.dfYOff =
671 3 : psExtraArg->dfYOff - iY * m_nMetaTileHeight;
672 3 : sExtraArgs.dfXSize = psExtraArg->dfXSize;
673 3 : sExtraArgs.dfYSize = psExtraArg->dfYSize;
674 : }
675 : }
676 23 : CPLDebugOnly("STACTA", "Reading %d,%d,%d,%d in %s", nTileXOff,
677 : nTileYOff, nTileXSize, nTileYSize, osURL.c_str());
678 23 : if (poTileDS->RasterIO(
679 : GF_Read, nTileXOff, nTileYOff, nTileXSize, nTileYSize,
680 23 : static_cast<GByte *>(pData) + nBufXOff * nPixelSpace +
681 23 : nBufYOff * nLineSpace,
682 : nBufXSizeEffective, nBufYSizeEffective, eBufType,
683 : nBandCount, panBandMap, nPixelSpace, nLineSpace,
684 23 : nBandSpace, &sExtraArgs) != CE_None)
685 : {
686 0 : return CE_Failure;
687 : }
688 : } while (false);
689 :
690 25 : if (bMissingTile)
691 : {
692 2 : CPLDebugOnly("STACTA", "Missing metatile %s", osURL.c_str());
693 8 : for (int iBand = 0; iBand < nBandCount; iBand++)
694 : {
695 6 : int bHasNoData = FALSE;
696 6 : double dfNodata = GetRasterBand(panBandMap[iBand])
697 6 : ->GetNoDataValue(&bHasNoData);
698 6 : if (!bHasNoData)
699 0 : dfNodata = 0;
700 6150 : for (int nYBufOff = 0; nYBufOff < nBufYSizeEffective;
701 : nYBufOff++)
702 : {
703 6144 : GByte *pabyDest = static_cast<GByte *>(pData) +
704 6144 : iBand * nBandSpace +
705 6144 : nBufXOff * nPixelSpace +
706 6144 : (nBufYOff + nYBufOff) * nLineSpace;
707 6144 : GDALCopyWords(&dfNodata, GDT_Float64, 0, pabyDest,
708 : eBufType, static_cast<int>(nPixelSpace),
709 : nBufXSizeEffective);
710 : }
711 : }
712 : }
713 :
714 25 : if (iX == nMinBlockX)
715 : {
716 32 : nBufXOff = m_nMetaTileWidth -
717 16 : std::max(0, nXOff - nMinBlockX * m_nMetaTileWidth);
718 : }
719 : else
720 : {
721 9 : nBufXOff += m_nMetaTileWidth;
722 : }
723 : }
724 :
725 15 : if (iY == nMinBlockY)
726 : {
727 30 : nBufYOff = m_nMetaTileHeight -
728 15 : std::max(0, nYOff - nMinBlockY * m_nMetaTileHeight);
729 : }
730 : else
731 : {
732 0 : nBufYOff += m_nMetaTileHeight;
733 : }
734 : }
735 :
736 15 : return CE_None;
737 : }
738 :
739 : /************************************************************************/
740 : /* GetGeoTransform() */
741 : /************************************************************************/
742 :
743 4 : CPLErr STACTARawDataset::GetGeoTransform(GDALGeoTransform >) const
744 : {
745 4 : gt = m_gt;
746 4 : return CE_None;
747 : }
748 :
749 : /************************************************************************/
750 : /* Identify() */
751 : /************************************************************************/
752 :
753 56332 : int STACTADataset::Identify(GDALOpenInfo *poOpenInfo)
754 : {
755 56332 : if (STARTS_WITH(poOpenInfo->pszFilename, "STACTA:"))
756 : {
757 6 : return true;
758 : }
759 :
760 56326 : const bool bIsSingleDriver = poOpenInfo->IsSingleAllowedDriver("STACTA");
761 56323 : if (bIsSingleDriver && (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
762 4 : STARTS_WITH(poOpenInfo->pszFilename, "https://")))
763 : {
764 1 : return true;
765 : }
766 :
767 56322 : if (
768 : #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
769 56856 : (!bIsSingleDriver && !poOpenInfo->IsExtensionEqualToCI("json")) ||
770 : #endif
771 534 : poOpenInfo->nHeaderBytes == 0)
772 : {
773 56140 : return false;
774 : }
775 :
776 437 : for (int i = 0; i < 2; i++)
777 : {
778 : // TryToIngest() may reallocate pabyHeader, so do not move this
779 : // before the loop.
780 308 : const char *pszHeader =
781 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
782 308 : while (*pszHeader != 0 &&
783 308 : std::isspace(static_cast<unsigned char>(*pszHeader)))
784 0 : ++pszHeader;
785 308 : if (bIsSingleDriver)
786 : {
787 4 : return pszHeader[0] == '{';
788 : }
789 :
790 304 : if (strstr(pszHeader, "\"stac_extensions\"") != nullptr &&
791 74 : (strstr(pszHeader, "\"tiled-assets\"") != nullptr ||
792 56 : strstr(
793 : pszHeader,
794 : "https:\\/\\/stac-extensions.github.io\\/tiled-assets\\/") !=
795 56 : nullptr ||
796 56 : strstr(pszHeader,
797 : "https://stac-extensions.github.io/tiled-assets/") !=
798 : nullptr))
799 : {
800 46 : return true;
801 : }
802 :
803 258 : if (i == 0)
804 : {
805 : // Should be enough for a STACTA .json file
806 129 : poOpenInfo->TryToIngest(32768);
807 : }
808 : }
809 :
810 129 : return false;
811 : }
812 :
813 : /************************************************************************/
814 : /* Open() */
815 : /************************************************************************/
816 :
817 25 : bool STACTADataset::Open(GDALOpenInfo *poOpenInfo)
818 : {
819 50 : CPLString osFilename(poOpenInfo->pszFilename);
820 50 : CPLString osAssetName;
821 50 : CPLString osTMS;
822 25 : if (STARTS_WITH(poOpenInfo->pszFilename, "STACTA:"))
823 : {
824 : const CPLStringList aosTokens(CSLTokenizeString2(
825 3 : poOpenInfo->pszFilename, ":", CSLT_HONOURSTRINGS));
826 4 : if (aosTokens.size() != 2 && aosTokens.size() != 3 &&
827 1 : aosTokens.size() != 4)
828 0 : return false;
829 3 : osFilename = aosTokens[1];
830 3 : if (aosTokens.size() >= 3)
831 3 : osAssetName = aosTokens[2];
832 3 : if (aosTokens.size() == 4)
833 1 : osTMS = aosTokens[3];
834 : }
835 :
836 50 : CPLJSONDocument oDoc;
837 50 : if (STARTS_WITH(osFilename, "http://") ||
838 25 : STARTS_WITH(osFilename, "https://"))
839 : {
840 0 : if (!oDoc.LoadUrl(osFilename, nullptr))
841 0 : return false;
842 : }
843 : else
844 : {
845 25 : if (!oDoc.Load(osFilename))
846 0 : return false;
847 : }
848 50 : const auto oRoot = oDoc.GetRoot();
849 75 : const auto oProperties = oRoot["properties"];
850 50 : if (!oProperties.IsValid() ||
851 25 : oProperties.GetType() != CPLJSONObject::Type::Object)
852 : {
853 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing properties");
854 0 : return false;
855 : }
856 :
857 75 : const auto oAssetTemplates = oRoot["asset_templates"];
858 50 : if (!oAssetTemplates.IsValid() ||
859 25 : oAssetTemplates.GetType() != CPLJSONObject::Type::Object)
860 : {
861 0 : CPLError(CE_Failure, CPLE_AppDefined, "Missing asset_templates");
862 0 : return false;
863 : }
864 :
865 50 : const auto aoAssetTemplates = oAssetTemplates.GetChildren();
866 25 : if (aoAssetTemplates.size() == 0)
867 : {
868 0 : CPLError(CE_Failure, CPLE_AppDefined, "Empty asset_templates");
869 0 : return false;
870 : }
871 :
872 75 : const auto oTMSs = oProperties.GetObj("tiles:tile_matrix_sets");
873 25 : if (!oTMSs.IsValid() || oTMSs.GetType() != CPLJSONObject::Type::Object)
874 : {
875 0 : CPLError(CE_Failure, CPLE_AppDefined,
876 : "Missing properties[\"tiles:tile_matrix_sets\"]");
877 0 : return false;
878 : }
879 50 : const auto aoTMSs = oTMSs.GetChildren();
880 25 : if (aoTMSs.empty())
881 : {
882 0 : CPLError(CE_Failure, CPLE_AppDefined,
883 : "Empty properties[\"tiles:tile_matrix_sets\"]");
884 0 : return false;
885 : }
886 :
887 52 : if ((aoAssetTemplates.size() >= 2 || aoTMSs.size() >= 2) &&
888 52 : osAssetName.empty() && osTMS.empty())
889 : {
890 2 : int nSDSCount = 0;
891 5 : for (const auto &oAssetTemplate : aoAssetTemplates)
892 : {
893 6 : const CPLString osAssetNameSubDS = oAssetTemplate.GetName();
894 3 : const char *pszAssetNameSubDS = osAssetNameSubDS.c_str();
895 3 : if (aoTMSs.size() >= 2)
896 : {
897 3 : for (const auto &oTMS : aoTMSs)
898 : {
899 2 : const CPLString osTMSSubDS = oTMS.GetName();
900 2 : const char *pszTMSSubDS = osTMSSubDS.c_str();
901 2 : GDALDataset::SetMetadataItem(
902 : CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
903 : CPLSPrintf("STACTA:\"%s\":%s:%s", osFilename.c_str(),
904 : pszAssetNameSubDS, pszTMSSubDS),
905 : "SUBDATASETS");
906 2 : GDALDataset::SetMetadataItem(
907 : CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
908 : CPLSPrintf("Asset %s, tile matrix set %s",
909 : pszAssetNameSubDS, pszTMSSubDS),
910 : "SUBDATASETS");
911 2 : nSDSCount++;
912 : }
913 : }
914 : else
915 : {
916 2 : GDALDataset::SetMetadataItem(
917 : CPLSPrintf("SUBDATASET_%d_NAME", nSDSCount + 1),
918 : CPLSPrintf("STACTA:\"%s\":%s", osFilename.c_str(),
919 : pszAssetNameSubDS),
920 : "SUBDATASETS");
921 2 : GDALDataset::SetMetadataItem(
922 : CPLSPrintf("SUBDATASET_%d_DESC", nSDSCount + 1),
923 : CPLSPrintf("Asset %s", pszAssetNameSubDS), "SUBDATASETS");
924 2 : nSDSCount++;
925 : }
926 : }
927 2 : return true;
928 : }
929 :
930 23 : if (osAssetName.empty())
931 : {
932 20 : osAssetName = aoAssetTemplates[0].GetName();
933 : }
934 46 : const auto oAssetTemplate = oAssetTemplates.GetObj(osAssetName);
935 46 : if (!oAssetTemplate.IsValid() ||
936 23 : oAssetTemplate.GetType() != CPLJSONObject::Type::Object)
937 : {
938 0 : CPLError(CE_Failure, CPLE_AppDefined,
939 : "Cannot find asset_templates[\"%s\"]", osAssetName.c_str());
940 0 : return false;
941 : }
942 :
943 23 : if (osTMS.empty())
944 : {
945 22 : osTMS = aoTMSs[0].GetName();
946 : }
947 46 : const auto oTMS = oTMSs.GetObj(osTMS);
948 23 : if (!oTMS.IsValid() || oTMS.GetType() != CPLJSONObject::Type::Object)
949 : {
950 0 : CPLError(CE_Failure, CPLE_AppDefined,
951 : "Cannot find properties[\"tiles:tile_matrix_sets\"][\"%s\"]",
952 : osTMS.c_str());
953 0 : return false;
954 : }
955 :
956 : auto poTMS = gdal::TileMatrixSet::parse(
957 46 : oTMS.Format(CPLJSONObject::PrettyFormat::Plain).c_str());
958 23 : if (poTMS == nullptr)
959 0 : return false;
960 :
961 69 : CPLString osURLTemplate = oAssetTemplate.GetString("href");
962 23 : if (osURLTemplate.empty())
963 : {
964 0 : CPLError(CE_Failure, CPLE_AppDefined,
965 : "Cannot find asset_templates[\"%s\"][\"href\"]",
966 : osAssetName.c_str());
967 : }
968 23 : osURLTemplate.replaceAll("{TileMatrixSet}", osTMS);
969 :
970 : // UPDATE oMapVSIToURIPrefix in apps/gdalalg_raster_tile if updating below
971 : const std::map<std::string, std::string> oMapURIPrefixToVSI = {
972 : {"s3", "/vsis3/"},
973 : {"gs", "/vsigs/"},
974 : {"az", "/vsiaz/"}, // Not universally recognized
975 : {"azure", "/vsiaz/"}, // Not universally recognized
976 161 : };
977 :
978 23 : if (cpl::starts_with(osURLTemplate, "file://"))
979 : {
980 0 : osURLTemplate = osURLTemplate.substr(strlen("file://"));
981 : }
982 : else
983 : {
984 23 : const auto nPosColonSlashSlash = osURLTemplate.find("://");
985 23 : if (nPosColonSlashSlash != std::string::npos)
986 : {
987 : const auto oIter = oMapURIPrefixToVSI.find(
988 3 : osURLTemplate.substr(0, nPosColonSlashSlash));
989 3 : if (oIter != oMapURIPrefixToVSI.end())
990 : {
991 0 : osURLTemplate = std::string(oIter->second)
992 0 : .append(osURLTemplate.substr(
993 0 : nPosColonSlashSlash + strlen("://")));
994 : }
995 : }
996 : }
997 :
998 43 : if (!cpl::starts_with(osURLTemplate, "http://") &&
999 20 : !cpl::starts_with(osURLTemplate, "https://"))
1000 : {
1001 20 : if (STARTS_WITH(osURLTemplate, "./"))
1002 20 : osURLTemplate = osURLTemplate.substr(2);
1003 40 : osURLTemplate = CPLProjectRelativeFilenameSafe(
1004 40 : CPLGetDirnameSafe(osFilename).c_str(), osURLTemplate);
1005 : }
1006 :
1007 : // Parse optional tile matrix set limits
1008 46 : std::map<CPLString, Limits> oMapLimits;
1009 69 : const auto oTMLinks = oProperties.GetObj("tiles:tile_matrix_links");
1010 23 : if (oTMLinks.IsValid())
1011 : {
1012 20 : if (oTMLinks.GetType() != CPLJSONObject::Type::Object)
1013 : {
1014 0 : CPLError(
1015 : CE_Failure, CPLE_AppDefined,
1016 : "Invalid type for properties[\"tiles:tile_matrix_links\"]");
1017 0 : return false;
1018 : }
1019 :
1020 60 : auto oLimits = oTMLinks[osTMS]["limits"];
1021 40 : if (oLimits.IsValid() &&
1022 20 : oLimits.GetType() == CPLJSONObject::Type::Object)
1023 : {
1024 76 : for (const auto &oLimit : oLimits.GetChildren())
1025 : {
1026 56 : Limits limits;
1027 56 : limits.min_tile_col = oLimit.GetInteger("min_tile_col");
1028 56 : limits.max_tile_col = oLimit.GetInteger("max_tile_col");
1029 56 : limits.min_tile_row = oLimit.GetInteger("min_tile_row");
1030 56 : limits.max_tile_row = oLimit.GetInteger("max_tile_row");
1031 56 : oMapLimits[oLimit.GetName()] = limits;
1032 : }
1033 : }
1034 : }
1035 23 : const auto &tmsList = poTMS->tileMatrixList();
1036 23 : if (tmsList.empty())
1037 0 : return false;
1038 :
1039 46 : m_bSkipMissingMetaTile = CPLTestBool(CSLFetchNameValueDef(
1040 23 : poOpenInfo->papszOpenOptions, "SKIP_MISSING_METATILE",
1041 : CPLGetConfigOption("GDAL_STACTA_SKIP_MISSING_METATILE", "NO")));
1042 :
1043 : // STAC 1.1 uses bands instead of eo:bands and raster:bands
1044 69 : const auto oBands = oAssetTemplate.GetArray("bands");
1045 :
1046 : // Check if there are both eo:bands and raster:bands extension
1047 : // If so, we don't need to fetch a prototype metatile to derive the
1048 : // information we need (number of bands, data type and nodata value)
1049 : const auto oEoBands =
1050 66 : oBands.IsValid() ? oBands : oAssetTemplate.GetArray("eo:bands");
1051 : const auto oRasterBands =
1052 66 : oBands.IsValid() ? oBands : oAssetTemplate.GetArray("raster:bands");
1053 :
1054 46 : std::vector<GDALDataType> aeDT;
1055 46 : std::vector<double> adfNoData;
1056 46 : std::vector<bool> abSetNoData;
1057 23 : int nExpectedBandCount = 0;
1058 23 : if (oRasterBands.IsValid())
1059 : {
1060 11 : if (oEoBands.IsValid() && oEoBands.Size() != oRasterBands.Size())
1061 : {
1062 1 : CPLError(CE_Warning, CPLE_AppDefined,
1063 : "Number of bands in eo:bands and raster:bands is not "
1064 : "identical. Ignoring the later");
1065 : }
1066 : else
1067 : {
1068 10 : nExpectedBandCount = oRasterBands.Size();
1069 :
1070 : const struct
1071 : {
1072 : const char *pszStacDataType;
1073 : GDALDataType eGDALDataType;
1074 10 : } aDataTypeMapping[] = {
1075 : {"int8", GDT_Int8},
1076 : {"int16", GDT_Int16},
1077 : {"int32", GDT_Int32},
1078 : {"int64", GDT_Int64},
1079 : {"uint8", GDT_Byte},
1080 : {"uint16", GDT_UInt16},
1081 : {"uint32", GDT_UInt32},
1082 : {"uint64", GDT_UInt64},
1083 : // float16: 16-bit float; unhandled
1084 : {"float32", GDT_Float32},
1085 : {"float64", GDT_Float64},
1086 : {"cint16", GDT_CInt16},
1087 : {"cint32", GDT_CInt32},
1088 : {"cfloat32", GDT_CFloat32},
1089 : {"cfloat64", GDT_CFloat64},
1090 : };
1091 :
1092 42 : for (int i = 0; i < nExpectedBandCount; ++i)
1093 : {
1094 35 : if (oRasterBands[i].GetType() != CPLJSONObject::Type::Object)
1095 : {
1096 1 : CPLError(CE_Failure, CPLE_AppDefined,
1097 : "Wrong raster:bands[%d]", i);
1098 3 : return false;
1099 : }
1100 : const std::string osDataType =
1101 68 : oRasterBands[i].GetString("data_type");
1102 34 : GDALDataType eDT = GDT_Unknown;
1103 290 : for (const auto &oTuple : aDataTypeMapping)
1104 : {
1105 288 : if (osDataType == oTuple.pszStacDataType)
1106 : {
1107 32 : eDT = oTuple.eGDALDataType;
1108 32 : break;
1109 : }
1110 : }
1111 34 : if (eDT == GDT_Unknown)
1112 : {
1113 2 : CPLError(CE_Failure, CPLE_AppDefined,
1114 : "Wrong raster:bands[%d].data_type = %s", i,
1115 : osDataType.c_str());
1116 2 : return false;
1117 : }
1118 32 : aeDT.push_back(eDT);
1119 :
1120 96 : const auto oNoData = oRasterBands[i].GetObj("nodata");
1121 32 : if (oNoData.GetType() == CPLJSONObject::Type::String)
1122 : {
1123 48 : const std::string osNoData = oNoData.ToString();
1124 16 : if (osNoData == "inf")
1125 : {
1126 5 : abSetNoData.push_back(true);
1127 5 : adfNoData.push_back(
1128 5 : std::numeric_limits<double>::infinity());
1129 : }
1130 11 : else if (osNoData == "-inf")
1131 : {
1132 5 : abSetNoData.push_back(true);
1133 5 : adfNoData.push_back(
1134 5 : -std::numeric_limits<double>::infinity());
1135 : }
1136 6 : else if (osNoData == "nan")
1137 : {
1138 5 : abSetNoData.push_back(true);
1139 5 : adfNoData.push_back(
1140 5 : std::numeric_limits<double>::quiet_NaN());
1141 : }
1142 : else
1143 : {
1144 1 : CPLError(CE_Warning, CPLE_AppDefined,
1145 : "Invalid raster:bands[%d].nodata = %s", i,
1146 : osNoData.c_str());
1147 1 : abSetNoData.push_back(false);
1148 1 : adfNoData.push_back(
1149 1 : std::numeric_limits<double>::quiet_NaN());
1150 : }
1151 : }
1152 16 : else if (oNoData.GetType() == CPLJSONObject::Type::Integer ||
1153 29 : oNoData.GetType() == CPLJSONObject::Type::Long ||
1154 13 : oNoData.GetType() == CPLJSONObject::Type::Double)
1155 : {
1156 8 : abSetNoData.push_back(true);
1157 8 : adfNoData.push_back(oNoData.ToDouble());
1158 : }
1159 8 : else if (!oNoData.IsValid())
1160 : {
1161 7 : abSetNoData.push_back(false);
1162 7 : adfNoData.push_back(
1163 7 : std::numeric_limits<double>::quiet_NaN());
1164 : }
1165 : else
1166 : {
1167 1 : CPLError(CE_Warning, CPLE_AppDefined,
1168 : "Invalid raster:bands[%d].nodata", i);
1169 1 : abSetNoData.push_back(false);
1170 1 : adfNoData.push_back(
1171 1 : std::numeric_limits<double>::quiet_NaN());
1172 : }
1173 : }
1174 :
1175 7 : CPLAssert(aeDT.size() == abSetNoData.size());
1176 7 : CPLAssert(adfNoData.size() == abSetNoData.size());
1177 : }
1178 : }
1179 :
1180 20 : std::unique_ptr<GDALDataset> poProtoDS;
1181 20 : if (aeDT.empty())
1182 : {
1183 13 : for (int i = 0; i < static_cast<int>(tmsList.size()); i++)
1184 : {
1185 : // Open a metatile to get mostly its band data type
1186 13 : int nProtoTileCol = 0;
1187 13 : int nProtoTileRow = 0;
1188 13 : auto oIterLimit = oMapLimits.find(tmsList[i].mId);
1189 13 : if (oIterLimit != oMapLimits.end())
1190 : {
1191 10 : nProtoTileCol = oIterLimit->second.min_tile_col;
1192 10 : nProtoTileRow = oIterLimit->second.min_tile_row;
1193 : }
1194 : const CPLString osURL =
1195 13 : CPLString(osURLTemplate)
1196 26 : .replaceAll("{TileMatrix}", tmsList[i].mId)
1197 26 : .replaceAll("{TileRow}", CPLSPrintf("%d", nProtoTileRow))
1198 26 : .replaceAll("{TileCol}", CPLSPrintf("%d", nProtoTileCol));
1199 38 : CPLString osProtoDSName = (STARTS_WITH(osURL, "http://") ||
1200 12 : STARTS_WITH(osURL, "https://"))
1201 26 : ? CPLString("/vsicurl/" + osURL)
1202 26 : : osURL;
1203 : CPLConfigOptionSetter oSetter("GDAL_DISABLE_READDIR_ON_OPEN",
1204 : "EMPTY_DIR",
1205 13 : /* bSetOnlyIfUndefined = */ true);
1206 13 : if (m_bSkipMissingMetaTile)
1207 2 : CPLPushErrorHandler(CPLQuietErrorHandler);
1208 13 : poProtoDS.reset(GDALDataset::Open(osProtoDSName.c_str(),
1209 : GDAL_OF_RASTER,
1210 26 : GetAllowedDrivers().List()));
1211 13 : if (m_bSkipMissingMetaTile)
1212 2 : CPLPopErrorHandler();
1213 13 : if (poProtoDS != nullptr)
1214 : {
1215 11 : break;
1216 : }
1217 :
1218 4 : if (!m_bTriedVSICLOUDSubstitution &&
1219 2 : cpl::starts_with(osURL, "https://"))
1220 : {
1221 0 : m_bTriedVSICLOUDSubstitution = true;
1222 0 : std::string osNewURL = DoVSICLOUDSubstitution(osURL);
1223 0 : if (!osNewURL.empty())
1224 : {
1225 0 : CPLDebug("STACTA", "Retrying with %s", osNewURL.c_str());
1226 0 : if (m_bSkipMissingMetaTile)
1227 0 : CPLPushErrorHandler(CPLQuietErrorHandler);
1228 0 : poProtoDS.reset(
1229 : GDALDataset::Open(osNewURL.c_str(), GDAL_OF_RASTER,
1230 0 : GetAllowedDrivers().List()));
1231 0 : if (m_bSkipMissingMetaTile)
1232 0 : CPLPopErrorHandler();
1233 0 : if (poProtoDS != nullptr)
1234 : {
1235 0 : osURLTemplate = DoVSICLOUDSubstitution(osURLTemplate);
1236 0 : break;
1237 : }
1238 : }
1239 : }
1240 :
1241 2 : if (!m_bSkipMissingMetaTile)
1242 : {
1243 2 : CPLError(CE_Failure, CPLE_OpenFailed, "Cannot open %s",
1244 : osURL.c_str());
1245 2 : return false;
1246 : }
1247 : }
1248 11 : if (poProtoDS == nullptr)
1249 : {
1250 0 : if (m_bSkipMissingMetaTile)
1251 : {
1252 0 : CPLError(CE_Failure, CPLE_AppDefined,
1253 : "Cannot find prototype dataset");
1254 0 : return false;
1255 : }
1256 : }
1257 : else
1258 : {
1259 11 : nExpectedBandCount = poProtoDS->GetRasterCount();
1260 : }
1261 : }
1262 :
1263 : // Iterate over tile matrices to create corresponding STACTARawDataset
1264 : // objects
1265 68 : for (int i = static_cast<int>(tmsList.size() - 1); i >= 0; i--)
1266 : {
1267 50 : const auto &oTM = tmsList[i];
1268 50 : int nMatrixWidth = oTM.mMatrixWidth;
1269 50 : int nMatrixHeight = oTM.mMatrixHeight;
1270 50 : auto oIterLimit = oMapLimits.find(tmsList[i].mId);
1271 50 : if (oIterLimit != oMapLimits.end())
1272 : {
1273 41 : nMatrixWidth = oIterLimit->second.max_tile_col -
1274 41 : oIterLimit->second.min_tile_col + 1;
1275 41 : nMatrixHeight = oIterLimit->second.max_tile_row -
1276 41 : oIterLimit->second.min_tile_row + 1;
1277 : }
1278 50 : if (nMatrixWidth <= 0 || oTM.mTileWidth > INT_MAX / nMatrixWidth ||
1279 50 : nMatrixHeight <= 0 || oTM.mTileHeight > INT_MAX / nMatrixHeight)
1280 : {
1281 0 : continue;
1282 : }
1283 50 : auto poRawDS = std::make_unique<STACTARawDataset>();
1284 100 : if (!poRawDS->InitRaster(poProtoDS.get(), aeDT, abSetNoData, adfNoData,
1285 50 : poTMS.get(), tmsList[i].mId, oTM, oMapLimits))
1286 : {
1287 0 : return false;
1288 : }
1289 50 : poRawDS->m_osURLTemplate = osURLTemplate;
1290 50 : poRawDS->m_osURLTemplate.replaceAll("{TileMatrix}", tmsList[i].mId);
1291 50 : poRawDS->m_poMasterDS = this;
1292 :
1293 50 : if (m_poDS == nullptr)
1294 : {
1295 18 : nRasterXSize = poRawDS->GetRasterXSize();
1296 18 : nRasterYSize = poRawDS->GetRasterYSize();
1297 18 : m_oSRS = poRawDS->m_oSRS;
1298 18 : m_gt = poRawDS->m_gt;
1299 18 : m_poDS = std::move(poRawDS);
1300 : }
1301 : else
1302 : {
1303 32 : const double dfMinX = m_gt[0];
1304 32 : const double dfMaxX = m_gt[0] + GetRasterXSize() * m_gt[1];
1305 32 : const double dfMaxY = m_gt[3];
1306 32 : const double dfMinY = m_gt[3] + GetRasterYSize() * m_gt[5];
1307 :
1308 32 : const double dfOvrMinX = poRawDS->m_gt[0];
1309 : const double dfOvrMaxX =
1310 32 : poRawDS->m_gt[0] + poRawDS->GetRasterXSize() * poRawDS->m_gt[1];
1311 32 : const double dfOvrMaxY = poRawDS->m_gt[3];
1312 : const double dfOvrMinY =
1313 32 : poRawDS->m_gt[3] + poRawDS->GetRasterYSize() * poRawDS->m_gt[5];
1314 :
1315 32 : if (fabs(dfMinX - dfOvrMinX) < 1e-10 * fabs(dfMinX) &&
1316 30 : fabs(dfMinY - dfOvrMinY) < 1e-10 * fabs(dfMinY) &&
1317 30 : fabs(dfMaxX - dfOvrMaxX) < 1e-10 * fabs(dfMaxX) &&
1318 30 : fabs(dfMaxY - dfOvrMaxY) < 1e-10 * fabs(dfMaxY))
1319 : {
1320 30 : m_apoOverviewDS.emplace_back(std::move(poRawDS));
1321 : }
1322 : else
1323 : {
1324 : // If this zoom level doesn't share the same origin and extent
1325 : // as the most resoluted one, then subset it
1326 2 : CPLStringList aosOptions;
1327 2 : aosOptions.AddString("-of");
1328 2 : aosOptions.AddString("VRT");
1329 2 : aosOptions.AddString("-projwin");
1330 2 : aosOptions.AddString(CPLSPrintf("%.17g", dfMinX));
1331 2 : aosOptions.AddString(CPLSPrintf("%.17g", dfMaxY));
1332 2 : aosOptions.AddString(CPLSPrintf("%.17g", dfMaxX));
1333 2 : aosOptions.AddString(CPLSPrintf("%.17g", dfMinY));
1334 : auto psOptions =
1335 2 : GDALTranslateOptionsNew(aosOptions.List(), nullptr);
1336 : auto hDS =
1337 2 : GDALTranslate("", GDALDataset::ToHandle(poRawDS.get()),
1338 : psOptions, nullptr);
1339 2 : GDALTranslateOptionsFree(psOptions);
1340 2 : if (hDS == nullptr)
1341 0 : continue;
1342 2 : m_apoIntermediaryDS.emplace_back(std::move(poRawDS));
1343 2 : m_apoOverviewDS.emplace_back(GDALDataset::FromHandle(hDS));
1344 : }
1345 : }
1346 : }
1347 18 : if (m_poDS == nullptr)
1348 : {
1349 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find valid tile matrix");
1350 0 : return false;
1351 : }
1352 :
1353 : // Create main bands
1354 83 : for (int i = 0; i < m_poDS->GetRasterCount(); i++)
1355 : {
1356 65 : auto poSrcBand = m_poDS->GetRasterBand(i + 1);
1357 65 : auto poBand = new STACTARasterBand(this, i + 1, poSrcBand);
1358 65 : if (oEoBands.IsValid() && oEoBands.Size() == nExpectedBandCount)
1359 : {
1360 : // Set band metadata
1361 50 : if (oEoBands[i].GetType() == CPLJSONObject::Type::Object)
1362 : {
1363 142 : for (const auto &oItem : oEoBands[i].GetChildren())
1364 : {
1365 92 : if (oBands.IsValid())
1366 : {
1367 : // STAC 1.1
1368 26 : if (STARTS_WITH(oItem.GetName().c_str(), "eo:"))
1369 : {
1370 2 : poBand->GDALRasterBand::SetMetadataItem(
1371 2 : oItem.GetName().c_str() + strlen("eo:"),
1372 2 : oItem.ToString().c_str());
1373 : }
1374 42 : else if (oItem.GetName() != "data_type" &&
1375 54 : oItem.GetName() != "nodata" &&
1376 48 : oItem.GetName() != "unit" &&
1377 46 : oItem.GetName() != "raster:scale" &&
1378 77 : oItem.GetName() != "raster:offset" &&
1379 34 : oItem.GetName() != "raster:bits_per_sample")
1380 : {
1381 16 : poBand->GDALRasterBand::SetMetadataItem(
1382 16 : oItem.GetName().c_str(),
1383 16 : oItem.ToString().c_str());
1384 : }
1385 : }
1386 : else
1387 : {
1388 : // STAC 1.0
1389 132 : poBand->GDALRasterBand::SetMetadataItem(
1390 198 : oItem.GetName().c_str(), oItem.ToString().c_str());
1391 : }
1392 : }
1393 : }
1394 : }
1395 227 : if (oRasterBands.IsValid() &&
1396 97 : oRasterBands.Size() == nExpectedBandCount &&
1397 97 : oRasterBands[i].GetType() == CPLJSONObject::Type::Object)
1398 : {
1399 32 : poBand->m_osUnit = oRasterBands[i].GetString("unit");
1400 64 : const double dfScale = oRasterBands[i].GetDouble(
1401 32 : oBands.IsValid() ? "raster:scale" : "scale");
1402 32 : if (dfScale != 0)
1403 5 : poBand->m_dfScale = dfScale;
1404 64 : poBand->m_dfOffset = oRasterBands[i].GetDouble(
1405 32 : oBands.IsValid() ? "raster:offset" : "offset");
1406 64 : const int nBitsPerSample = oRasterBands[i].GetInteger(
1407 32 : oBands.IsValid() ? "raster:bits_per_sample"
1408 : : "bits_per_sample");
1409 5 : if (((nBitsPerSample >= 1 && nBitsPerSample <= 7) &&
1410 37 : poBand->GetRasterDataType() == GDT_Byte) ||
1411 0 : ((nBitsPerSample >= 9 && nBitsPerSample <= 15) &&
1412 0 : poBand->GetRasterDataType() == GDT_UInt16))
1413 : {
1414 5 : poBand->GDALRasterBand::SetMetadataItem(
1415 : "NBITS", CPLSPrintf("%d", nBitsPerSample),
1416 : "IMAGE_STRUCTURE");
1417 : }
1418 : }
1419 65 : SetBand(i + 1, poBand);
1420 : }
1421 :
1422 : // Set dataset metadata
1423 127 : for (const auto &oItem : oProperties.GetChildren())
1424 : {
1425 218 : const auto osName = oItem.GetName();
1426 203 : if (osName != "tiles:tile_matrix_links" &&
1427 203 : osName != "tiles:tile_matrix_sets" &&
1428 76 : !cpl::starts_with(osName, "proj:"))
1429 : {
1430 70 : GDALDataset::SetMetadataItem(osName.c_str(),
1431 140 : oItem.ToString().c_str());
1432 : }
1433 : }
1434 :
1435 18 : if (poProtoDS)
1436 : {
1437 : const char *pszInterleave =
1438 11 : poProtoDS->GetMetadataItem("INTERLEAVE", "IMAGE_STRUCTURE");
1439 11 : GDALDataset::SetMetadataItem("INTERLEAVE",
1440 : pszInterleave ? pszInterleave : "PIXEL",
1441 : "IMAGE_STRUCTURE");
1442 : }
1443 : else
1444 : {
1445 : // A bit bold to assume that, but that should be a reasonable
1446 : // setting
1447 7 : GDALDataset::SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
1448 : }
1449 :
1450 18 : m_bDownloadWholeMetaTile = CPLTestBool(CSLFetchNameValueDef(
1451 18 : poOpenInfo->papszOpenOptions, "WHOLE_METATILE", "NO"));
1452 :
1453 18 : return true;
1454 : }
1455 :
1456 : /************************************************************************/
1457 : /* ~STACTADataset() */
1458 : /************************************************************************/
1459 :
1460 50 : STACTADataset::~STACTADataset()
1461 : {
1462 25 : m_poDS.reset();
1463 25 : m_apoOverviewDS.clear();
1464 25 : m_apoIntermediaryDS.clear();
1465 50 : }
1466 :
1467 : /************************************************************************/
1468 : /* FlushCache() */
1469 : /************************************************************************/
1470 :
1471 0 : CPLErr STACTADataset::FlushCache(bool bAtClosing)
1472 : {
1473 0 : m_oCacheTileDS.clear();
1474 0 : return GDALDataset::FlushCache(bAtClosing);
1475 : }
1476 :
1477 : /************************************************************************/
1478 : /* InitRaster() */
1479 : /************************************************************************/
1480 :
1481 50 : bool STACTARawDataset::InitRaster(GDALDataset *poProtoDS,
1482 : const std::vector<GDALDataType> &aeDT,
1483 : const std::vector<bool> &abSetNoData,
1484 : const std::vector<double> &adfNoData,
1485 : const gdal::TileMatrixSet *poTMS,
1486 : const std::string &osTMId,
1487 : const gdal::TileMatrixSet::TileMatrix &oTM,
1488 : const std::map<CPLString, Limits> &oMapLimits)
1489 : {
1490 50 : int nMatrixWidth = oTM.mMatrixWidth;
1491 50 : int nMatrixHeight = oTM.mMatrixHeight;
1492 50 : auto oIterLimit = oMapLimits.find(osTMId);
1493 50 : if (oIterLimit != oMapLimits.end())
1494 : {
1495 41 : m_nMinMetaTileCol = oIterLimit->second.min_tile_col;
1496 41 : m_nMinMetaTileRow = oIterLimit->second.min_tile_row;
1497 41 : nMatrixWidth = oIterLimit->second.max_tile_col - m_nMinMetaTileCol + 1;
1498 41 : nMatrixHeight = oIterLimit->second.max_tile_row - m_nMinMetaTileRow + 1;
1499 : }
1500 50 : m_nMetaTileWidth = oTM.mTileWidth;
1501 50 : m_nMetaTileHeight = oTM.mTileHeight;
1502 50 : nRasterXSize = nMatrixWidth * m_nMetaTileWidth;
1503 50 : nRasterYSize = nMatrixHeight * m_nMetaTileHeight;
1504 :
1505 50 : if (poProtoDS)
1506 : {
1507 132 : for (int i = 0; i < poProtoDS->GetRasterCount(); i++)
1508 : {
1509 99 : auto poProtoBand = poProtoDS->GetRasterBand(i + 1);
1510 99 : auto poBand = new STACTARawRasterBand(this, i + 1, poProtoBand);
1511 99 : SetBand(i + 1, poBand);
1512 : }
1513 : }
1514 : else
1515 : {
1516 109 : for (int i = 0; i < static_cast<int>(aeDT.size()); i++)
1517 : {
1518 92 : auto poBand = new STACTARawRasterBand(this, i + 1, aeDT[i],
1519 92 : abSetNoData[i], adfNoData[i]);
1520 92 : SetBand(i + 1, poBand);
1521 : }
1522 : }
1523 :
1524 100 : CPLString osCRS = poTMS->crs().c_str();
1525 50 : if (osCRS == "http://www.opengis.net/def/crs/OGC/1.3/CRS84")
1526 48 : osCRS = "EPSG:4326";
1527 50 : if (m_oSRS.SetFromUserInput(osCRS) != OGRERR_NONE)
1528 : {
1529 0 : return false;
1530 : }
1531 50 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1532 50 : m_gt[0] = oTM.mTopLeftX + m_nMinMetaTileCol * m_nMetaTileWidth * oTM.mResX;
1533 50 : m_gt[1] = oTM.mResX;
1534 50 : m_gt[3] = oTM.mTopLeftY - m_nMinMetaTileRow * m_nMetaTileHeight * oTM.mResY;
1535 50 : m_gt[5] = -oTM.mResY;
1536 50 : SetMetadataItem("INTERLEAVE", "PIXEL", "IMAGE_STRUCTURE");
1537 :
1538 50 : return true;
1539 : }
1540 :
1541 : /************************************************************************/
1542 : /* GetSpatialRef () */
1543 : /************************************************************************/
1544 :
1545 5 : const OGRSpatialReference *STACTADataset::GetSpatialRef() const
1546 : {
1547 5 : return nBands == 0 ? nullptr : &m_oSRS;
1548 : }
1549 :
1550 : /************************************************************************/
1551 : /* GetGeoTransform() */
1552 : /************************************************************************/
1553 :
1554 5 : CPLErr STACTADataset::GetGeoTransform(GDALGeoTransform >) const
1555 : {
1556 5 : gt = m_gt;
1557 5 : return nBands == 0 ? CE_Failure : CE_None;
1558 : }
1559 :
1560 : /************************************************************************/
1561 : /* OpenStatic() */
1562 : /************************************************************************/
1563 :
1564 25 : GDALDataset *STACTADataset::OpenStatic(GDALOpenInfo *poOpenInfo)
1565 : {
1566 25 : if (!Identify(poOpenInfo))
1567 0 : return nullptr;
1568 50 : auto poDS = std::make_unique<STACTADataset>();
1569 25 : if (!poDS->Open(poOpenInfo))
1570 5 : return nullptr;
1571 20 : return poDS.release();
1572 : }
1573 :
1574 : /************************************************************************/
1575 : /* GDALRegister_STACTA() */
1576 : /************************************************************************/
1577 :
1578 1964 : void GDALRegister_STACTA()
1579 :
1580 : {
1581 1964 : if (GDALGetDriverByName("STACTA") != nullptr)
1582 283 : return;
1583 :
1584 1681 : GDALDriver *poDriver = new GDALDriver();
1585 :
1586 1681 : poDriver->SetDescription("STACTA");
1587 1681 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1588 1681 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
1589 1681 : "Spatio-Temporal Asset Catalog Tiled Assets");
1590 1681 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/stacta.html");
1591 1681 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "json");
1592 1681 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1593 1681 : poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
1594 1681 : poDriver->SetMetadataItem(
1595 : GDAL_DMD_OPENOPTIONLIST,
1596 : "<OpenOptionList>"
1597 : " <Option name='WHOLE_METATILE' type='boolean' "
1598 : "description='Whether to download whole metatiles'/>"
1599 : " <Option name='SKIP_MISSING_METATILE' type='boolean' "
1600 : "description='Whether to gracefully skip missing metatiles'/>"
1601 1681 : "</OpenOptionList>");
1602 :
1603 1681 : poDriver->pfnOpen = STACTADataset::OpenStatic;
1604 1681 : poDriver->pfnIdentify = STACTADataset::Identify;
1605 :
1606 1681 : GetGDALDriverManager()->RegisterDriver(poDriver);
1607 : }
|