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