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_frmts.h"
18 : #include "gdal_utils.h"
19 : #include "memdataset.h"
20 : #include "tilematrixset.hpp"
21 : #include "stactadataset.h"
22 :
23 : #include <algorithm>
24 : #include <array>
25 : #include <limits>
26 : #include <map>
27 : #include <memory>
28 : #include <vector>
29 :
30 : // Implements a driver for
31 : // https://github.com/stac-extensions/tiled-assets
32 :
33 : /************************************************************************/
34 : /* GetAllowedDrivers() */
35 : /************************************************************************/
36 :
37 31 : static CPLStringList GetAllowedDrivers()
38 : {
39 31 : CPLStringList aosAllowedDrivers;
40 31 : aosAllowedDrivers.AddString("GTiff");
41 31 : aosAllowedDrivers.AddString("PNG");
42 31 : aosAllowedDrivers.AddString("JPEG");
43 31 : aosAllowedDrivers.AddString("JPEGXL");
44 31 : aosAllowedDrivers.AddString("WEBP");
45 31 : aosAllowedDrivers.AddString("JP2KAK");
46 31 : aosAllowedDrivers.AddString("JP2ECW");
47 31 : aosAllowedDrivers.AddString("JP2MrSID");
48 31 : aosAllowedDrivers.AddString("JP2OpenJPEG");
49 31 : return aosAllowedDrivers;
50 : }
51 :
52 : /************************************************************************/
53 : /* STACTARasterBand() */
54 : /************************************************************************/
55 :
56 65 : STACTARasterBand::STACTARasterBand(STACTADataset *poDSIn, int nBandIn,
57 65 : GDALRasterBand *poProtoBand)
58 65 : : m_eColorInterp(poProtoBand->GetColorInterpretation())
59 : {
60 65 : poDS = poDSIn;
61 65 : nBand = nBandIn;
62 65 : eDataType = poProtoBand->GetRasterDataType();
63 65 : poProtoBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
64 65 : nRasterXSize = poDSIn->GetRasterXSize();
65 65 : nRasterYSize = poDSIn->GetRasterYSize();
66 65 : m_dfNoData = poProtoBand->GetNoDataValue(&m_bHasNoDataValue);
67 65 : }
68 :
69 : /************************************************************************/
70 : /* IReadBlock() */
71 : /************************************************************************/
72 :
73 0 : CPLErr STACTARasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
74 : void *pImage)
75 : {
76 0 : auto poGDS = cpl::down_cast<STACTADataset *>(poDS);
77 0 : return poGDS->m_poDS->GetRasterBand(nBand)->ReadBlock(nBlockXOff,
78 0 : nBlockYOff, pImage);
79 : }
80 :
81 : /************************************************************************/
82 : /* IRasterIO() */
83 : /************************************************************************/
84 :
85 6 : CPLErr STACTARasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
86 : int nXSize, int nYSize, void *pData,
87 : int nBufXSize, int nBufYSize,
88 : GDALDataType eBufType, GSpacing nPixelSpace,
89 : GSpacing nLineSpace,
90 : GDALRasterIOExtraArg *psExtraArg)
91 : {
92 6 : auto poGDS = cpl::down_cast<STACTADataset *>(poDS);
93 6 : if ((nBufXSize < nXSize || nBufYSize < nYSize) &&
94 12 : poGDS->m_apoOverviewDS.size() >= 1 && eRWFlag == GF_Read)
95 : {
96 : int bTried;
97 1 : CPLErr eErr = TryOverviewRasterIO(
98 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
99 : eBufType, nPixelSpace, nLineSpace, psExtraArg, &bTried);
100 1 : if (bTried)
101 1 : return eErr;
102 : }
103 :
104 5 : return poGDS->m_poDS->GetRasterBand(nBand)->RasterIO(
105 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
106 5 : eBufType, nPixelSpace, nLineSpace, psExtraArg);
107 : }
108 :
109 : /************************************************************************/
110 : /* IRasterIO() */
111 : /************************************************************************/
112 :
113 12 : CPLErr STACTADataset::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
114 : int nXSize, int nYSize, void *pData,
115 : int nBufXSize, int nBufYSize,
116 : GDALDataType eBufType, int nBandCount,
117 : BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
118 : GSpacing nLineSpace, GSpacing nBandSpace,
119 : GDALRasterIOExtraArg *psExtraArg)
120 : {
121 12 : if ((nBufXSize < nXSize || nBufYSize < nYSize) &&
122 24 : m_apoOverviewDS.size() >= 1 && eRWFlag == GF_Read)
123 : {
124 : int bTried;
125 6 : CPLErr eErr = TryOverviewRasterIO(
126 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
127 : eBufType, nBandCount, panBandMap, nPixelSpace, nLineSpace,
128 : nBandSpace, psExtraArg, &bTried);
129 6 : if (bTried)
130 3 : return eErr;
131 : }
132 :
133 9 : return m_poDS->RasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
134 : nBufXSize, nBufYSize, eBufType, nBandCount,
135 : panBandMap, nPixelSpace, nLineSpace, nBandSpace,
136 9 : psExtraArg);
137 : }
138 :
139 : /************************************************************************/
140 : /* GetOverviewCount() */
141 : /************************************************************************/
142 :
143 34 : int STACTARasterBand::GetOverviewCount()
144 : {
145 34 : STACTADataset *poGDS = cpl::down_cast<STACTADataset *>(poDS);
146 34 : return static_cast<int>(poGDS->m_apoOverviewDS.size());
147 : }
148 :
149 : /************************************************************************/
150 : /* GetOverview() */
151 : /************************************************************************/
152 :
153 24 : GDALRasterBand *STACTARasterBand::GetOverview(int nIdx)
154 : {
155 24 : STACTADataset *poGDS = cpl::down_cast<STACTADataset *>(poDS);
156 24 : if (nIdx < 0 || nIdx >= GetOverviewCount())
157 0 : return nullptr;
158 24 : return poGDS->m_apoOverviewDS[nIdx]->GetRasterBand(nBand);
159 : }
160 :
161 : /************************************************************************/
162 : /* GetNoDataValue() */
163 : /************************************************************************/
164 :
165 20 : double STACTARasterBand::GetNoDataValue(int *pbHasNoData)
166 : {
167 20 : if (pbHasNoData)
168 20 : *pbHasNoData = m_bHasNoDataValue;
169 20 : return m_dfNoData;
170 : }
171 :
172 : /************************************************************************/
173 : /* STACTARawRasterBand() */
174 : /************************************************************************/
175 :
176 99 : STACTARawRasterBand::STACTARawRasterBand(STACTARawDataset *poDSIn, int nBandIn,
177 99 : GDALRasterBand *poProtoBand)
178 99 : : m_eColorInterp(poProtoBand->GetColorInterpretation())
179 : {
180 99 : poDS = poDSIn;
181 99 : nBand = nBandIn;
182 99 : eDataType = poProtoBand->GetRasterDataType();
183 99 : nBlockXSize = 256;
184 99 : nBlockYSize = 256;
185 : int nProtoBlockXSize;
186 : int nProtoBlockYSize;
187 : // Use tile block size if it divides the metatile dimension.
188 99 : poProtoBand->GetBlockSize(&nProtoBlockXSize, &nProtoBlockYSize);
189 99 : if ((poDSIn->m_nMetaTileWidth % nProtoBlockXSize) == 0 &&
190 99 : (poDSIn->m_nMetaTileHeight % nProtoBlockYSize) == 0)
191 : {
192 99 : nBlockXSize = nProtoBlockXSize;
193 99 : nBlockYSize = nProtoBlockYSize;
194 : }
195 99 : nRasterXSize = poDSIn->GetRasterXSize();
196 99 : nRasterYSize = poDSIn->GetRasterYSize();
197 99 : m_dfNoData = poProtoBand->GetNoDataValue(&m_bHasNoDataValue);
198 99 : }
199 :
200 : /************************************************************************/
201 : /* STACTARawRasterBand() */
202 : /************************************************************************/
203 :
204 92 : STACTARawRasterBand::STACTARawRasterBand(STACTARawDataset *poDSIn, int nBandIn,
205 : GDALDataType eDT, bool bSetNoData,
206 92 : double dfNoData)
207 : {
208 92 : poDS = poDSIn;
209 92 : nBand = nBandIn;
210 92 : eDataType = eDT;
211 92 : nBlockXSize = 256;
212 92 : nBlockYSize = 256;
213 92 : nRasterXSize = poDSIn->GetRasterXSize();
214 92 : nRasterYSize = poDSIn->GetRasterYSize();
215 92 : m_bHasNoDataValue = bSetNoData;
216 92 : m_dfNoData = dfNoData;
217 92 : }
218 :
219 : /************************************************************************/
220 : /* GetNoDataValue() */
221 : /************************************************************************/
222 :
223 95 : double STACTARawRasterBand::GetNoDataValue(int *pbHasNoData)
224 : {
225 95 : if (pbHasNoData)
226 89 : *pbHasNoData = m_bHasNoDataValue;
227 95 : return m_dfNoData;
228 : }
229 :
230 : /************************************************************************/
231 : /* IReadBlock() */
232 : /************************************************************************/
233 :
234 0 : CPLErr STACTARawRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
235 : void *pImage)
236 : {
237 0 : const int nXOff = nBlockXOff * nBlockXSize;
238 0 : const int nYOff = nBlockYOff * nBlockYSize;
239 0 : const int nXSize = std::min(nBlockXSize, nRasterXSize - nXOff);
240 0 : const int nYSize = std::min(nBlockYSize, nRasterYSize - nYOff);
241 : GDALRasterIOExtraArg sExtraArgs;
242 0 : INIT_RASTERIO_EXTRA_ARG(sExtraArgs);
243 0 : const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
244 0 : return IRasterIO(GF_Read, nXOff, nYOff, nXSize, nYSize, pImage, nBlockXSize,
245 : nBlockYSize, eDataType, nDTSize,
246 0 : static_cast<GSpacing>(nDTSize) * nBlockXSize, &sExtraArgs);
247 : }
248 :
249 : /************************************************************************/
250 : /* IRasterIO() */
251 : /************************************************************************/
252 :
253 6 : CPLErr STACTARawRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
254 : int nXSize, int nYSize, void *pData,
255 : int nBufXSize, int nBufYSize,
256 : GDALDataType eBufType,
257 : GSpacing nPixelSpace, GSpacing nLineSpace,
258 : GDALRasterIOExtraArg *psExtraArg)
259 : {
260 6 : CPLDebugOnly("STACTA", "Band %d RasterIO: %d,%d,%d,%d->%d,%d", nBand, nXOff,
261 : nYOff, nXSize, nYSize, nBufXSize, nBufYSize);
262 6 : auto poGDS = cpl::down_cast<STACTARawDataset *>(poDS);
263 :
264 6 : const int nKernelRadius = 3; // up to 3 for Lanczos
265 : const int nRadiusX =
266 6 : nKernelRadius * static_cast<int>(std::ceil(nXSize / nBufXSize));
267 : const int nRadiusY =
268 6 : nKernelRadius * static_cast<int>(std::ceil(nYSize / nBufYSize));
269 6 : const int nXOffMod = std::max(0, nXOff - nRadiusX);
270 6 : const int nYOffMod = std::max(0, nYOff - nRadiusY);
271 6 : const int nXSizeMod = static_cast<int>(std::min(
272 12 : nXOff + nXSize + static_cast<GIntBig>(nRadiusX),
273 6 : static_cast<GIntBig>(nRasterXSize))) -
274 6 : nXOffMod;
275 6 : const int nYSizeMod = static_cast<int>(std::min(
276 12 : nYOff + nYSize + static_cast<GIntBig>(nRadiusY),
277 6 : static_cast<GIntBig>(nRasterYSize))) -
278 6 : nYOffMod;
279 :
280 6 : const bool bRequestFitsInSingleMetaTile =
281 6 : nXOffMod / poGDS->m_nMetaTileWidth ==
282 9 : (nXOffMod + nXSizeMod - 1) / poGDS->m_nMetaTileWidth &&
283 3 : nYOffMod / poGDS->m_nMetaTileHeight ==
284 3 : (nYOffMod + nYSizeMod - 1) / poGDS->m_nMetaTileHeight;
285 :
286 6 : if (eRWFlag != GF_Read || ((nXSize != nBufXSize || nYSize != nBufYSize) &&
287 1 : !bRequestFitsInSingleMetaTile))
288 : {
289 0 : if (!(eRWFlag == GF_Read && nXSizeMod <= 4096 && nYSizeMod <= 4096))
290 : {
291 : // If not reading at nominal resolution, fallback to default block
292 : // reading
293 0 : return GDALRasterBand::IRasterIO(
294 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize,
295 0 : nBufYSize, eBufType, nPixelSpace, nLineSpace, psExtraArg);
296 : }
297 : }
298 :
299 : // Use optimized dataset level RasterIO()
300 6 : return poGDS->IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
301 : nBufXSize, nBufYSize, eBufType, 1, &nBand,
302 6 : nPixelSpace, nLineSpace, 0, psExtraArg);
303 : }
304 :
305 : /************************************************************************/
306 : /* DoVSICLOUDSubstitution() */
307 : /************************************************************************/
308 :
309 0 : static std::string DoVSICLOUDSubstitution(const std::string &osFilename)
310 : {
311 0 : std::string ret;
312 0 : constexpr const char *HTTPS_PROTOCOL = "https://";
313 0 : if (cpl::starts_with(osFilename, HTTPS_PROTOCOL))
314 : {
315 0 : constexpr const char *AZURE_BLOB = ".blob.core.windows.net/";
316 0 : constexpr const char *AWS = ".amazonaws.com/";
317 0 : constexpr const char *GOOGLE_CLOUD_STORAGE =
318 : "https://storage.googleapis.com/";
319 : size_t nPos;
320 0 : if ((nPos = osFilename.find(AZURE_BLOB)) != std::string::npos)
321 : {
322 0 : ret = "/vsiaz/" + osFilename.substr(nPos + strlen(AZURE_BLOB));
323 : }
324 0 : else if ((nPos = osFilename.find(AWS)) != std::string::npos)
325 : {
326 0 : constexpr const char *DOT_S3_DOT = ".s3.";
327 0 : const auto nPos2 = osFilename.find(DOT_S3_DOT);
328 0 : if (nPos2 != std::string::npos)
329 : {
330 0 : ret = "/vsis3/" +
331 0 : osFilename.substr(strlen(HTTPS_PROTOCOL),
332 0 : nPos2 - strlen(HTTPS_PROTOCOL)) +
333 0 : "/" + osFilename.substr(nPos + strlen(AWS));
334 : }
335 : }
336 0 : else if (cpl::starts_with(osFilename, GOOGLE_CLOUD_STORAGE))
337 : {
338 0 : ret = "/vsigs/" + osFilename.substr(strlen(GOOGLE_CLOUD_STORAGE));
339 : }
340 : }
341 0 : return ret;
342 : }
343 :
344 : /************************************************************************/
345 : /* IRasterIO() */
346 : /************************************************************************/
347 :
348 19 : CPLErr STACTARawDataset::IRasterIO(
349 : GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
350 : void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
351 : int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
352 : GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
353 : {
354 19 : CPLDebugOnly("STACTA", "Dataset RasterIO: %d,%d,%d,%d->%d,%d", nXOff, nYOff,
355 : nXSize, nYSize, nBufXSize, nBufYSize);
356 19 : const int nMinBlockX = nXOff / m_nMetaTileWidth;
357 19 : const int nMaxBlockX = (nXOff + nXSize - 1) / m_nMetaTileWidth;
358 19 : const int nMinBlockY = nYOff / m_nMetaTileHeight;
359 19 : const int nMaxBlockY = (nYOff + nYSize - 1) / m_nMetaTileHeight;
360 :
361 19 : const int nKernelRadius = 3; // up to 3 for Lanczos
362 : const int nRadiusX =
363 19 : nKernelRadius * static_cast<int>(std::ceil(nXSize / nBufXSize));
364 : const int nRadiusY =
365 19 : nKernelRadius * static_cast<int>(std::ceil(nYSize / nBufYSize));
366 19 : const int nXOffMod = std::max(0, nXOff - nRadiusX);
367 19 : const int nYOffMod = std::max(0, nYOff - nRadiusY);
368 19 : const int nXSizeMod = static_cast<int>(std::min(
369 38 : nXOff + nXSize + static_cast<GIntBig>(nRadiusX),
370 19 : static_cast<GIntBig>(nRasterXSize))) -
371 19 : nXOffMod;
372 19 : const int nYSizeMod = static_cast<int>(std::min(
373 38 : nYOff + nYSize + static_cast<GIntBig>(nRadiusY),
374 19 : static_cast<GIntBig>(nRasterYSize))) -
375 19 : nYOffMod;
376 :
377 19 : const bool bRequestFitsInSingleMetaTile =
378 19 : nXOffMod / m_nMetaTileWidth ==
379 27 : (nXOffMod + nXSizeMod - 1) / m_nMetaTileWidth &&
380 8 : nYOffMod / m_nMetaTileHeight ==
381 8 : (nYOffMod + nYSizeMod - 1) / m_nMetaTileHeight;
382 19 : const auto eBandDT = GetRasterBand(1)->GetRasterDataType();
383 19 : const int nDTSize = GDALGetDataTypeSizeBytes(eBandDT);
384 :
385 19 : if (eRWFlag != GF_Read || ((nXSize != nBufXSize || nYSize != nBufYSize) &&
386 7 : !bRequestFitsInSingleMetaTile))
387 : {
388 1 : if (eRWFlag == GF_Read && nXSizeMod <= 4096 && nYSizeMod <= 4096 &&
389 : nBandCount <= 10)
390 : {
391 : // If extracting from a small enough window, do a RasterIO()
392 : // at full resolution into a MEM dataset, and then proceeding to
393 : // resampling on it. This will avoid to fallback on block based
394 : // approach.
395 : GDALRasterIOExtraArg sExtraArgs;
396 1 : INIT_RASTERIO_EXTRA_ARG(sExtraArgs);
397 1 : const size_t nXSizeModeMulYSizeModMulDTSize =
398 1 : static_cast<size_t>(nXSizeMod) * nYSizeMod * nDTSize;
399 : std::vector<GByte> abyBuf(nXSizeModeMulYSizeModMulDTSize *
400 2 : nBandCount);
401 1 : if (IRasterIO(GF_Read, nXOffMod, nYOffMod, nXSizeMod, nYSizeMod,
402 1 : &abyBuf[0], nXSizeMod, nYSizeMod, eBandDT, nBandCount,
403 : panBandMap, nDTSize,
404 1 : static_cast<GSpacing>(nDTSize) * nXSizeMod,
405 1 : static_cast<GSpacing>(nDTSize) * nXSizeMod *
406 1 : nYSizeMod,
407 1 : &sExtraArgs) != CE_None)
408 : {
409 0 : return CE_Failure;
410 : }
411 :
412 : auto poMEMDS = std::unique_ptr<MEMDataset>(MEMDataset::Create(
413 2 : "", nXSizeMod, nYSizeMod, 0, eBandDT, nullptr));
414 4 : for (int i = 0; i < nBandCount; i++)
415 : {
416 3 : auto hBand = MEMCreateRasterBandEx(
417 3 : poMEMDS.get(), i + 1,
418 3 : &abyBuf[0] + i * nXSizeModeMulYSizeModMulDTSize, eBandDT, 0,
419 : 0, false);
420 3 : poMEMDS->AddMEMBand(hBand);
421 : }
422 :
423 1 : sExtraArgs.eResampleAlg = psExtraArg->eResampleAlg;
424 1 : if (psExtraArg->bFloatingPointWindowValidity)
425 : {
426 0 : sExtraArgs.bFloatingPointWindowValidity = true;
427 0 : sExtraArgs.dfXOff = psExtraArg->dfXOff - nXOffMod;
428 0 : sExtraArgs.dfYOff = psExtraArg->dfYOff - nYOffMod;
429 0 : sExtraArgs.dfXSize = psExtraArg->dfXSize;
430 0 : sExtraArgs.dfYSize = psExtraArg->dfYSize;
431 : }
432 1 : return poMEMDS->RasterIO(
433 : GF_Read, nXOff - nXOffMod, nYOff - nYOffMod, nXSize, nYSize,
434 : pData, nBufXSize, nBufYSize, eBufType, nBandCount, nullptr,
435 1 : nPixelSpace, nLineSpace, nBandSpace, &sExtraArgs);
436 : }
437 :
438 : // If not reading at nominal resolution, fallback to default block
439 : // reading
440 0 : return GDALDataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
441 : pData, nBufXSize, nBufYSize, eBufType,
442 : nBandCount, panBandMap, nPixelSpace,
443 0 : nLineSpace, nBandSpace, psExtraArg);
444 : }
445 :
446 18 : int nBufYOff = 0;
447 :
448 : // If the (uncompressed) size of a metatile is small enough, then download
449 : // it entirely to minimize the number of network requests
450 18 : const bool bDownloadWholeMetaTile =
451 35 : m_poMasterDS->m_bDownloadWholeMetaTile ||
452 17 : (static_cast<GIntBig>(m_nMetaTileWidth) * m_nMetaTileHeight * nBands *
453 17 : nDTSize <
454 : 128 * 1024);
455 :
456 : // Split the request on each metatile that it intersects
457 33 : for (int iY = nMinBlockY; iY <= nMaxBlockY; iY++)
458 : {
459 18 : const int nTileYOff = std::max(0, nYOff - iY * m_nMetaTileHeight);
460 : const int nTileYSize =
461 18 : std::min((iY + 1) * m_nMetaTileHeight, nYOff + nYSize) -
462 18 : std::max(nYOff, iY * m_nMetaTileHeight);
463 :
464 18 : int nBufXOff = 0;
465 43 : for (int iX = nMinBlockX; iX <= nMaxBlockX; iX++)
466 : {
467 28 : CPLString osURL(m_osURLTemplate);
468 : osURL.replaceAll("{TileRow}",
469 28 : CPLSPrintf("%d", iY + m_nMinMetaTileRow));
470 : osURL.replaceAll("{TileCol}",
471 28 : CPLSPrintf("%d", iX + m_nMinMetaTileCol));
472 28 : if (m_poMasterDS->m_bVSICLOUDSubstitutionOK)
473 0 : osURL = DoVSICLOUDSubstitution(osURL);
474 :
475 28 : const int nTileXOff = std::max(0, nXOff - iX * m_nMetaTileWidth);
476 : const int nTileXSize =
477 28 : std::min((iX + 1) * m_nMetaTileWidth, nXOff + nXSize) -
478 28 : std::max(nXOff, iX * m_nMetaTileWidth);
479 :
480 28 : const int nBufXSizeEffective =
481 28 : bRequestFitsInSingleMetaTile ? nBufXSize : nTileXSize;
482 28 : const int nBufYSizeEffective =
483 28 : bRequestFitsInSingleMetaTile ? nBufYSize : nTileYSize;
484 :
485 28 : bool bMissingTile = false;
486 : do
487 : {
488 : std::unique_ptr<GDALDataset> *ppoTileDS =
489 28 : m_poMasterDS->m_oCacheTileDS.getPtr(osURL);
490 28 : if (ppoTileDS == nullptr)
491 : {
492 :
493 : // Avoid probing side car files
494 : CPLConfigOptionSetter oSetter(
495 : "GDAL_DISABLE_READDIR_ON_OPEN", "EMPTY_DIR",
496 18 : /* bSetOnlyIfUndefined = */ true);
497 :
498 18 : CPLStringList aosAllowedDrivers(GetAllowedDrivers());
499 0 : std::unique_ptr<GDALDataset> poTileDS;
500 18 : if (bDownloadWholeMetaTile && !VSIIsLocal(osURL.c_str()))
501 : {
502 0 : if (m_poMasterDS->m_bSkipMissingMetaTile)
503 0 : CPLPushErrorHandler(CPLQuietErrorHandler);
504 0 : VSILFILE *fp = VSIFOpenL(osURL, "rb");
505 0 : if (m_poMasterDS->m_bSkipMissingMetaTile)
506 0 : CPLPopErrorHandler();
507 0 : if (fp == nullptr)
508 : {
509 0 : if (!m_poMasterDS->m_bTriedVSICLOUDSubstitution &&
510 0 : cpl::starts_with(osURL, "https://"))
511 : {
512 0 : m_poMasterDS->m_bTriedVSICLOUDSubstitution =
513 : true;
514 : std::string osNewURL =
515 0 : DoVSICLOUDSubstitution(osURL);
516 0 : if (!osNewURL.empty())
517 : {
518 0 : CPLDebug("STACTA", "Retrying with %s",
519 : osNewURL.c_str());
520 0 : if (m_poMasterDS->m_bSkipMissingMetaTile)
521 0 : CPLPushErrorHandler(
522 : CPLQuietErrorHandler);
523 0 : fp = VSIFOpenL(osNewURL.c_str(), "rb");
524 0 : if (m_poMasterDS->m_bSkipMissingMetaTile)
525 0 : CPLPopErrorHandler();
526 0 : if (fp != nullptr)
527 : {
528 0 : VSIFCloseL(fp);
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 57611 : int STACTADataset::Identify(GDALOpenInfo *poOpenInfo)
754 : {
755 57611 : if (STARTS_WITH(poOpenInfo->pszFilename, "STACTA:"))
756 : {
757 6 : return true;
758 : }
759 :
760 57605 : const bool bIsSingleDriver = poOpenInfo->IsSingleAllowedDriver("STACTA");
761 57605 : if (bIsSingleDriver && (STARTS_WITH(poOpenInfo->pszFilename, "http://") ||
762 4 : STARTS_WITH(poOpenInfo->pszFilename, "https://")))
763 : {
764 1 : return true;
765 : }
766 :
767 57604 : if (
768 : #ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
769 58213 : (!bIsSingleDriver && !poOpenInfo->IsExtensionEqualToCI("json")) ||
770 : #endif
771 609 : poOpenInfo->nHeaderBytes == 0)
772 : {
773 57426 : return false;
774 : }
775 :
776 440 : for (int i = 0; i < 2; i++)
777 : {
778 : // TryToIngest() may reallocate pabyHeader, so do not move this
779 : // before the loop.
780 310 : const char *pszHeader =
781 : reinterpret_cast<const char *>(poOpenInfo->pabyHeader);
782 310 : while (*pszHeader != 0 &&
783 310 : std::isspace(static_cast<unsigned char>(*pszHeader)))
784 0 : ++pszHeader;
785 310 : if (bIsSingleDriver)
786 : {
787 4 : return pszHeader[0] == '{';
788 : }
789 :
790 306 : 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 260 : if (i == 0)
804 : {
805 : // Should be enough for a STACTA .json file
806 130 : poOpenInfo->TryToIngest(32768);
807 : }
808 : }
809 :
810 130 : 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 2033 : void GDALRegister_STACTA()
1579 :
1580 : {
1581 2033 : if (GDALGetDriverByName("STACTA") != nullptr)
1582 283 : return;
1583 :
1584 1750 : GDALDriver *poDriver = new GDALDriver();
1585 :
1586 1750 : poDriver->SetDescription("STACTA");
1587 1750 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1588 1750 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
1589 1750 : "Spatio-Temporal Asset Catalog Tiled Assets");
1590 1750 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/stacta.html");
1591 1750 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "json");
1592 1750 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1593 1750 : poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
1594 1750 : 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 1750 : "</OpenOptionList>");
1602 :
1603 1750 : poDriver->pfnOpen = STACTADataset::OpenStatic;
1604 1750 : poDriver->pfnIdentify = STACTADataset::Identify;
1605 :
1606 1750 : GetGDALDriverManager()->RegisterDriver(poDriver);
1607 : }
|