Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: Hierarchical Data Format Release 5 (HDF5)
4 : * Purpose: Read subdatasets of HDF5 file.
5 : * Author: Denis Nadeau <denis.nadeau@gmail.com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2005, Frank Warmerdam <warmerdam@pobox.com>
9 : * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
10 : *
11 : * SPDX-License-Identifier: MIT
12 : ****************************************************************************/
13 :
14 : #include "hdf5_api.h"
15 :
16 : #include "cpl_float.h"
17 : #include "cpl_string.h"
18 : #include "gdal_frmts.h"
19 : #include "gdal_pam.h"
20 : #include "gdal_priv.h"
21 : #include "gh5_convenience.h"
22 : #include "hdf5dataset.h"
23 : #include "hdf5drivercore.h"
24 : #include "ogr_spatialref.h"
25 : #include "memdataset.h"
26 :
27 : #include <algorithm>
28 : #include <cstring>
29 : #include <limits>
30 :
31 : class HDF5ImageDataset final : public HDF5Dataset
32 : {
33 : typedef enum
34 : {
35 : UNKNOWN_PRODUCT = 0,
36 : CSK_PRODUCT
37 : } Hdf5ProductType;
38 :
39 : typedef enum
40 : {
41 : PROD_UNKNOWN = 0,
42 : PROD_CSK_L0,
43 : PROD_CSK_L1A,
44 : PROD_CSK_L1B,
45 : PROD_CSK_L1C,
46 : PROD_CSK_L1D
47 : } HDF5CSKProductEnum;
48 :
49 : friend class HDF5ImageRasterBand;
50 :
51 : OGRSpatialReference m_oSRS{};
52 : OGRSpatialReference m_oGCPSRS{};
53 : std::vector<gdal::GCP> m_aoGCPs{};
54 :
55 : hsize_t *dims;
56 : hsize_t *maxdims;
57 : HDF5GroupObjects *poH5Objects;
58 : int ndims;
59 : int dimensions;
60 : hid_t dataset_id;
61 : hid_t dataspace_id;
62 : hid_t native;
63 : #ifdef HDF5_HAVE_FLOAT16
64 : bool m_bConvertFromFloat16 = false;
65 : #endif
66 : Hdf5ProductType iSubdatasetType;
67 : HDF5CSKProductEnum iCSKProductType;
68 : GDALGeoTransform m_gt{};
69 : bool bHasGeoTransform;
70 : int m_nXIndex = -1;
71 : int m_nYIndex = -1;
72 : int m_nOtherDimIndex = -1;
73 :
74 : int m_nBlockXSize = 0;
75 : int m_nBlockYSize = 0;
76 : int m_nBandChunkSize = 1; //! Number of bands in a chunk
77 :
78 : enum WholeBandChunkOptim
79 : {
80 : WBC_DETECTION_IN_PROGRESS,
81 : WBC_DISABLED,
82 : WBC_ENABLED,
83 : };
84 :
85 : //! Flag to detect if the read pattern of HDF5ImageRasterBand::IRasterIO()
86 : // is whole band after whole band.
87 : WholeBandChunkOptim m_eWholeBandChunkOptim = WBC_DETECTION_IN_PROGRESS;
88 : //! Value of nBand during last HDF5ImageRasterBand::IRasterIO() call
89 : int m_nLastRasterIOBand = -1;
90 : //! Value of nXOff during last HDF5ImageRasterBand::IRasterIO() call
91 : int m_nLastRasterIOXOff = -1;
92 : //! Value of nYOff during last HDF5ImageRasterBand::IRasterIO() call
93 : int m_nLastRasterIOYOff = -1;
94 : //! Value of nXSize during last HDF5ImageRasterBand::IRasterIO() call
95 : int m_nLastRasterIOXSize = -1;
96 : //! Value of nYSize during last HDF5ImageRasterBand::IRasterIO() call
97 : int m_nLastRasterIOYSize = -1;
98 : //! Value such that m_abyBandChunk represent band data in the range
99 : // [m_iCurrentBandChunk * m_nBandChunkSize, (m_iCurrentBandChunk+1) * m_nBandChunkSize[
100 : int m_iCurrentBandChunk = -1;
101 : //! Cached values (in native data type) for bands in the range
102 : // [m_iCurrentBandChunk * m_nBandChunkSize, (m_iCurrentBandChunk+1) * m_nBandChunkSize[
103 : std::vector<GByte> m_abyBandChunk{};
104 :
105 : CPLErr CreateODIMH5Projection();
106 :
107 : CPL_DISALLOW_COPY_ASSIGN(HDF5ImageDataset)
108 :
109 : public:
110 : HDF5ImageDataset();
111 : ~HDF5ImageDataset() override;
112 :
113 : CPLErr CreateProjections();
114 : static GDALDataset *Open(GDALOpenInfo *);
115 : static int Identify(GDALOpenInfo *);
116 :
117 : const OGRSpatialReference *GetSpatialRef() const override;
118 : int GetGCPCount() override;
119 : const OGRSpatialReference *GetGCPSpatialRef() const override;
120 : const GDAL_GCP *GetGCPs() override;
121 : CPLErr GetGeoTransform(GDALGeoTransform >) const override;
122 :
123 : CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize,
124 : int nYSize, void *pData, int nBufXSize, int nBufYSize,
125 : GDALDataType eBufType, int nBandCount,
126 : BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
127 : GSpacing nLineSpace, GSpacing nBandSpace,
128 : GDALRasterIOExtraArg *psExtraArg) override;
129 :
130 : const char *GetMetadataItem(const char *pszName,
131 : const char *pszDomain = "") override;
132 :
133 177 : Hdf5ProductType GetSubdatasetType() const
134 : {
135 177 : return iSubdatasetType;
136 : }
137 :
138 6 : HDF5CSKProductEnum GetCSKProductType() const
139 : {
140 6 : return iCSKProductType;
141 : }
142 :
143 177 : int IsComplexCSKL1A() const
144 : {
145 183 : return GetSubdatasetType() == CSK_PRODUCT &&
146 183 : GetCSKProductType() == PROD_CSK_L1A && ndims == 3;
147 : }
148 :
149 566 : int GetYIndex() const
150 : {
151 566 : return m_nYIndex;
152 : }
153 :
154 700 : int GetXIndex() const
155 : {
156 700 : return m_nXIndex;
157 : }
158 :
159 : /**
160 : * Identify if the subdataset has a known product format
161 : * It stores a product identifier in iSubdatasetType,
162 : * UNKNOWN_PRODUCT, if it isn't a recognizable format.
163 : */
164 : void IdentifyProductType();
165 :
166 : /**
167 : * Captures Geolocation information from a COSMO-SKYMED
168 : * file.
169 : * The geoid will always be WGS84
170 : * The projection type may be UTM or UPS, depending on the
171 : * latitude from the center of the image.
172 : * @param iProductType type of HDF5 subproduct, see HDF5CSKProduct
173 : */
174 : void CaptureCSKGeolocation(int iProductType);
175 :
176 : /**
177 : * Get Geotransform information for COSMO-SKYMED files
178 : * In case of success it stores the transformation
179 : * in m_gt. In case of failure it doesn't
180 : * modify m_gt
181 : * @param iProductType type of HDF5 subproduct, see HDF5CSKProduct
182 : */
183 : void CaptureCSKGeoTransform(int iProductType);
184 :
185 : /**
186 : * @param iProductType type of HDF5 subproduct, see HDF5CSKProduct
187 : */
188 : void CaptureCSKGCPs(int iProductType);
189 : };
190 :
191 : /************************************************************************/
192 : /* ==================================================================== */
193 : /* HDF5ImageDataset */
194 : /* ==================================================================== */
195 : /************************************************************************/
196 :
197 : /************************************************************************/
198 : /* HDF5ImageDataset() */
199 : /************************************************************************/
200 60 : HDF5ImageDataset::HDF5ImageDataset()
201 : : dims(nullptr), maxdims(nullptr), poH5Objects(nullptr), ndims(0),
202 : dimensions(0), dataset_id(-1), dataspace_id(-1), native(-1),
203 : iSubdatasetType(UNKNOWN_PRODUCT), iCSKProductType(PROD_UNKNOWN),
204 60 : bHasGeoTransform(false)
205 : {
206 60 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
207 60 : m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
208 60 : }
209 :
210 : /************************************************************************/
211 : /* ~HDF5ImageDataset() */
212 : /************************************************************************/
213 120 : HDF5ImageDataset::~HDF5ImageDataset()
214 : {
215 : HDF5_GLOBAL_LOCK();
216 :
217 60 : FlushCache(true);
218 :
219 60 : if (dataset_id > 0)
220 60 : H5Dclose(dataset_id);
221 60 : if (dataspace_id > 0)
222 60 : H5Sclose(dataspace_id);
223 60 : if (native > 0)
224 59 : H5Tclose(native);
225 :
226 60 : CPLFree(dims);
227 60 : CPLFree(maxdims);
228 120 : }
229 :
230 : /************************************************************************/
231 : /* ==================================================================== */
232 : /* Hdf5imagerasterband */
233 : /* ==================================================================== */
234 : /************************************************************************/
235 : class HDF5ImageRasterBand final : public GDALPamRasterBand
236 : {
237 : friend class HDF5ImageDataset;
238 :
239 : bool bNoDataSet = false;
240 : double dfNoDataValue = -9999.0;
241 : bool m_bHasOffset = false;
242 : double m_dfOffset = 0.0;
243 : bool m_bHasScale = false;
244 : double m_dfScale = 1.0;
245 : int m_nIRasterIORecCounter = 0;
246 :
247 : public:
248 : HDF5ImageRasterBand(HDF5ImageDataset *, int, GDALDataType);
249 : ~HDF5ImageRasterBand() override;
250 :
251 : CPLErr IReadBlock(int, int, void *) override;
252 : double GetNoDataValue(int *) override;
253 : double GetOffset(int *) override;
254 : double GetScale(int *) override;
255 : // virtual CPLErr IWriteBlock( int, int, void * );
256 :
257 : CPLErr IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize,
258 : int nYSize, void *pData, int nBufXSize, int nBufYSize,
259 : GDALDataType eBufType, GSpacing nPixelSpace,
260 : GSpacing nLineSpace,
261 : GDALRasterIOExtraArg *psExtraArg) override;
262 : };
263 :
264 : /************************************************************************/
265 : /* ~HDF5ImageRasterBand() */
266 : /************************************************************************/
267 :
268 252 : HDF5ImageRasterBand::~HDF5ImageRasterBand()
269 : {
270 252 : }
271 :
272 : /************************************************************************/
273 : /* HDF5ImageRasterBand() */
274 : /************************************************************************/
275 126 : HDF5ImageRasterBand::HDF5ImageRasterBand(HDF5ImageDataset *poDSIn, int nBandIn,
276 126 : GDALDataType eType)
277 : {
278 126 : poDS = poDSIn;
279 126 : nBand = nBandIn;
280 126 : eDataType = eType;
281 126 : nBlockXSize = poDSIn->m_nBlockXSize;
282 126 : nBlockYSize = poDSIn->m_nBlockYSize;
283 :
284 : // netCDF convention for nodata
285 126 : bNoDataSet =
286 126 : GH5_FetchAttribute(poDSIn->dataset_id, "_FillValue", dfNoDataValue);
287 126 : if (!bNoDataSet)
288 117 : dfNoDataValue = -9999.0;
289 :
290 : // netCDF conventions for scale and offset
291 126 : m_bHasOffset =
292 126 : GH5_FetchAttribute(poDSIn->dataset_id, "add_offset", m_dfOffset);
293 126 : if (!m_bHasOffset)
294 125 : m_dfOffset = 0.0;
295 126 : m_bHasScale =
296 126 : GH5_FetchAttribute(poDSIn->dataset_id, "scale_factor", m_dfScale);
297 126 : if (!m_bHasScale)
298 125 : m_dfScale = 1.0;
299 126 : }
300 :
301 : /************************************************************************/
302 : /* GetNoDataValue() */
303 : /************************************************************************/
304 48 : double HDF5ImageRasterBand::GetNoDataValue(int *pbSuccess)
305 :
306 : {
307 48 : if (bNoDataSet)
308 : {
309 3 : if (pbSuccess)
310 3 : *pbSuccess = bNoDataSet;
311 :
312 3 : return dfNoDataValue;
313 : }
314 :
315 45 : return GDALPamRasterBand::GetNoDataValue(pbSuccess);
316 : }
317 :
318 : /************************************************************************/
319 : /* GetOffset() */
320 : /************************************************************************/
321 :
322 22 : double HDF5ImageRasterBand::GetOffset(int *pbSuccess)
323 :
324 : {
325 22 : if (m_bHasOffset)
326 : {
327 1 : if (pbSuccess)
328 1 : *pbSuccess = m_bHasOffset;
329 :
330 1 : return m_dfOffset;
331 : }
332 :
333 21 : return GDALPamRasterBand::GetOffset(pbSuccess);
334 : }
335 :
336 : /************************************************************************/
337 : /* GetScale() */
338 : /************************************************************************/
339 :
340 22 : double HDF5ImageRasterBand::GetScale(int *pbSuccess)
341 :
342 : {
343 22 : if (m_bHasScale)
344 : {
345 1 : if (pbSuccess)
346 1 : *pbSuccess = m_bHasScale;
347 :
348 1 : return m_dfScale;
349 : }
350 :
351 21 : return GDALPamRasterBand::GetScale(pbSuccess);
352 : }
353 :
354 : /************************************************************************/
355 : /* IReadBlock() */
356 : /************************************************************************/
357 103 : CPLErr HDF5ImageRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff,
358 : void *pImage)
359 : {
360 103 : HDF5ImageDataset *poGDS = static_cast<HDF5ImageDataset *>(poDS);
361 :
362 103 : memset(pImage, 0,
363 103 : static_cast<size_t>(nBlockXSize) * nBlockYSize *
364 103 : GDALGetDataTypeSizeBytes(eDataType));
365 :
366 103 : if (poGDS->eAccess == GA_Update)
367 : {
368 0 : return CE_None;
369 : }
370 :
371 103 : const int nXOff = nBlockXOff * nBlockXSize;
372 103 : const int nYOff = nBlockYOff * nBlockYSize;
373 103 : const int nXSize = std::min(nBlockXSize, nRasterXSize - nXOff);
374 103 : const int nYSize = std::min(nBlockYSize, nRasterYSize - nYOff);
375 103 : if (poGDS->m_eWholeBandChunkOptim == HDF5ImageDataset::WBC_ENABLED)
376 : {
377 : const bool bIsBandInterleavedData =
378 2 : poGDS->ndims == 3 && poGDS->m_nOtherDimIndex == 0 &&
379 3 : poGDS->GetYIndex() == 1 && poGDS->GetXIndex() == 2;
380 1 : if (poGDS->nBands == 1 || bIsBandInterleavedData)
381 : {
382 : GDALRasterIOExtraArg sExtraArg;
383 1 : INIT_RASTERIO_EXTRA_ARG(sExtraArg);
384 1 : const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
385 2 : return IRasterIO(GF_Read, nXOff, nYOff, nXSize, nYSize, pImage,
386 : nXSize, nYSize, eDataType, nDTSize,
387 1 : static_cast<GSpacing>(nDTSize) * nBlockXSize,
388 1 : &sExtraArg);
389 : }
390 : }
391 :
392 : HDF5_GLOBAL_LOCK();
393 :
394 102 : hsize_t count[3] = {0, 0, 0};
395 102 : H5OFFSET_TYPE offset[3] = {0, 0, 0};
396 102 : hsize_t col_dims[3] = {0, 0, 0};
397 102 : hsize_t rank = std::min(poGDS->ndims, 2);
398 :
399 102 : if (poGDS->ndims == 3)
400 : {
401 1 : rank = 3;
402 1 : offset[poGDS->m_nOtherDimIndex] = nBand - 1;
403 1 : count[poGDS->m_nOtherDimIndex] = 1;
404 1 : col_dims[poGDS->m_nOtherDimIndex] = 1;
405 : }
406 :
407 102 : const int nYIndex = poGDS->GetYIndex();
408 : // Blocksize may not be a multiple of imagesize.
409 102 : if (nYIndex >= 0)
410 : {
411 101 : offset[nYIndex] = nYOff;
412 101 : count[nYIndex] = nYSize;
413 : }
414 102 : offset[poGDS->GetXIndex()] = nXOff;
415 102 : count[poGDS->GetXIndex()] = nXSize;
416 :
417 : // Select block from file space.
418 102 : herr_t status = H5Sselect_hyperslab(poGDS->dataspace_id, H5S_SELECT_SET,
419 : offset, nullptr, count, nullptr);
420 102 : if (status < 0)
421 0 : return CE_Failure;
422 :
423 : // Create memory space to receive the data.
424 102 : if (nYIndex >= 0)
425 101 : col_dims[nYIndex] = nBlockYSize;
426 102 : col_dims[poGDS->GetXIndex()] = nBlockXSize;
427 :
428 : const hid_t memspace =
429 102 : H5Screate_simple(static_cast<int>(rank), col_dims, nullptr);
430 102 : H5OFFSET_TYPE mem_offset[3] = {0, 0, 0};
431 102 : status = H5Sselect_hyperslab(memspace, H5S_SELECT_SET, mem_offset, nullptr,
432 : count, nullptr);
433 102 : if (status < 0)
434 : {
435 0 : H5Sclose(memspace);
436 0 : return CE_Failure;
437 : }
438 :
439 102 : status = H5Dread(poGDS->dataset_id, poGDS->native, memspace,
440 : poGDS->dataspace_id, H5P_DEFAULT, pImage);
441 :
442 102 : H5Sclose(memspace);
443 :
444 102 : if (status < 0)
445 : {
446 0 : CPLError(CE_Failure, CPLE_AppDefined, "H5Dread() failed for block.");
447 0 : return CE_Failure;
448 : }
449 :
450 : #ifdef HDF5_HAVE_FLOAT16
451 : if (eDataType == GDT_Float32 && poGDS->m_bConvertFromFloat16)
452 : {
453 : for (size_t i = static_cast<size_t>(nBlockXSize) * nBlockYSize; i > 0;
454 : /* do nothing */)
455 : {
456 : --i;
457 : uint16_t nVal16;
458 : memcpy(&nVal16, static_cast<uint16_t *>(pImage) + i,
459 : sizeof(nVal16));
460 : const uint32_t nVal32 = CPLHalfToFloat(nVal16);
461 : float fVal;
462 : memcpy(&fVal, &nVal32, sizeof(fVal));
463 : *(static_cast<float *>(pImage) + i) = fVal;
464 : }
465 : }
466 : else if (eDataType == GDT_CFloat32 && poGDS->m_bConvertFromFloat16)
467 : {
468 : for (size_t i = static_cast<size_t>(nBlockXSize) * nBlockYSize; i > 0;
469 : /* do nothing */)
470 : {
471 : --i;
472 : for (int j = 1; j >= 0; --j)
473 : {
474 : uint16_t nVal16;
475 : memcpy(&nVal16, static_cast<uint16_t *>(pImage) + 2 * i + j,
476 : sizeof(nVal16));
477 : const uint32_t nVal32 = CPLHalfToFloat(nVal16);
478 : float fVal;
479 : memcpy(&fVal, &nVal32, sizeof(fVal));
480 : *(static_cast<float *>(pImage) + 2 * i + j) = fVal;
481 : }
482 : }
483 : }
484 : #endif
485 :
486 102 : return CE_None;
487 : }
488 :
489 : /************************************************************************/
490 : /* IRasterIO() */
491 : /************************************************************************/
492 :
493 311 : CPLErr HDF5ImageRasterBand::IRasterIO(GDALRWFlag eRWFlag, int nXOff, int nYOff,
494 : int nXSize, int nYSize, void *pData,
495 : int nBufXSize, int nBufYSize,
496 : GDALDataType eBufType,
497 : GSpacing nPixelSpace, GSpacing nLineSpace,
498 : GDALRasterIOExtraArg *psExtraArg)
499 :
500 : {
501 311 : HDF5ImageDataset *poGDS = static_cast<HDF5ImageDataset *>(poDS);
502 :
503 : #ifdef HDF5_HAVE_FLOAT16
504 : if (poGDS->m_bConvertFromFloat16)
505 : {
506 : return GDALPamRasterBand::IRasterIO(
507 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pData, nBufXSize, nBufYSize,
508 : eBufType, nPixelSpace, nLineSpace, psExtraArg);
509 : }
510 : #endif
511 :
512 : const bool bIsBandInterleavedData =
513 450 : poGDS->ndims == 3 && poGDS->m_nOtherDimIndex == 0 &&
514 761 : poGDS->GetYIndex() == 1 && poGDS->GetXIndex() == 2;
515 :
516 311 : const int nDTSize = GDALGetDataTypeSizeBytes(eDataType);
517 :
518 : // Try to detect if we read whole bands by chunks of whole lines
519 : // If so, then read and cache whole band (or group of m_nBandChunkSize bands)
520 : // to save HDF5 decompression.
521 311 : if (m_nIRasterIORecCounter == 0)
522 : {
523 263 : bool bInvalidateWholeBandChunkOptim = false;
524 263 : if (!(nXSize == nBufXSize && nYSize == nBufYSize))
525 : {
526 1 : bInvalidateWholeBandChunkOptim = true;
527 : }
528 : // Is the first request on band 1, line 0 and one or several full lines?
529 262 : else if (poGDS->m_eWholeBandChunkOptim !=
530 70 : HDF5ImageDataset::WBC_ENABLED &&
531 70 : nBand == 1 && nXOff == 0 && nYOff == 0 &&
532 20 : nXSize == nRasterXSize)
533 : {
534 19 : poGDS->m_eWholeBandChunkOptim =
535 : HDF5ImageDataset::WBC_DETECTION_IN_PROGRESS;
536 19 : poGDS->m_nLastRasterIOBand = 1;
537 19 : poGDS->m_nLastRasterIOXOff = nXOff;
538 19 : poGDS->m_nLastRasterIOYOff = nYOff;
539 19 : poGDS->m_nLastRasterIOXSize = nXSize;
540 19 : poGDS->m_nLastRasterIOYSize = nYSize;
541 : }
542 243 : else if (poGDS->m_eWholeBandChunkOptim ==
543 : HDF5ImageDataset::WBC_DETECTION_IN_PROGRESS)
544 : {
545 50 : if (poGDS->m_nLastRasterIOBand == 1 && nBand == 1)
546 : {
547 : // Is this request a continuation of the previous one?
548 44 : if (nXOff == 0 && poGDS->m_nLastRasterIOXOff == 0 &&
549 44 : nYOff == poGDS->m_nLastRasterIOYOff +
550 44 : poGDS->m_nLastRasterIOYSize &&
551 43 : poGDS->m_nLastRasterIOXSize == nRasterXSize &&
552 43 : nXSize == nRasterXSize)
553 : {
554 43 : poGDS->m_nLastRasterIOXOff = nXOff;
555 43 : poGDS->m_nLastRasterIOYOff = nYOff;
556 43 : poGDS->m_nLastRasterIOXSize = nXSize;
557 43 : poGDS->m_nLastRasterIOYSize = nYSize;
558 : }
559 : else
560 : {
561 1 : bInvalidateWholeBandChunkOptim = true;
562 : }
563 : }
564 6 : else if (poGDS->m_nLastRasterIOBand == 1 && nBand == 2)
565 : {
566 : // Are we switching to band 2 while having fully read band 1?
567 5 : if (nXOff == 0 && nYOff == 0 && nXSize == nRasterXSize &&
568 5 : poGDS->m_nLastRasterIOXOff == 0 &&
569 5 : poGDS->m_nLastRasterIOXSize == nRasterXSize &&
570 5 : poGDS->m_nLastRasterIOYOff + poGDS->m_nLastRasterIOYSize ==
571 5 : nRasterYSize)
572 : {
573 11 : if ((poGDS->m_nBandChunkSize > 1 ||
574 5 : nBufYSize < nRasterYSize) &&
575 1 : static_cast<int64_t>(poGDS->m_nBandChunkSize) *
576 1 : nRasterXSize * nRasterYSize * nDTSize <
577 1 : CPLGetUsablePhysicalRAM() / 10)
578 : {
579 1 : poGDS->m_eWholeBandChunkOptim =
580 : HDF5ImageDataset::WBC_ENABLED;
581 : }
582 : else
583 : {
584 3 : bInvalidateWholeBandChunkOptim = true;
585 : }
586 : }
587 : else
588 : {
589 1 : bInvalidateWholeBandChunkOptim = true;
590 : }
591 : }
592 : else
593 : {
594 1 : bInvalidateWholeBandChunkOptim = true;
595 : }
596 : }
597 263 : if (bInvalidateWholeBandChunkOptim)
598 : {
599 7 : poGDS->m_eWholeBandChunkOptim = HDF5ImageDataset::WBC_DISABLED;
600 7 : poGDS->m_nLastRasterIOBand = -1;
601 7 : poGDS->m_nLastRasterIOXOff = -1;
602 7 : poGDS->m_nLastRasterIOYOff = -1;
603 7 : poGDS->m_nLastRasterIOXSize = -1;
604 7 : poGDS->m_nLastRasterIOYSize = -1;
605 : }
606 : }
607 :
608 311 : if (poGDS->m_eWholeBandChunkOptim == HDF5ImageDataset::WBC_ENABLED &&
609 193 : nXSize == nBufXSize && nYSize == nBufYSize)
610 : {
611 193 : if (poGDS->nBands == 1 || bIsBandInterleavedData)
612 : {
613 193 : if (poGDS->m_iCurrentBandChunk < 0)
614 1 : CPLDebug("HDF5", "Using whole band chunk caching");
615 193 : const int iBandChunk = (nBand - 1) / poGDS->m_nBandChunkSize;
616 193 : if (iBandChunk != poGDS->m_iCurrentBandChunk)
617 : {
618 15 : poGDS->m_abyBandChunk.resize(
619 15 : static_cast<size_t>(poGDS->m_nBandChunkSize) *
620 15 : nRasterXSize * nRasterYSize * nDTSize);
621 :
622 : HDF5_GLOBAL_LOCK();
623 :
624 : hsize_t count[3] = {
625 30 : std::min(static_cast<hsize_t>(poGDS->nBands),
626 30 : static_cast<hsize_t>(iBandChunk + 1) *
627 15 : poGDS->m_nBandChunkSize) -
628 15 : static_cast<hsize_t>(iBandChunk) *
629 15 : poGDS->m_nBandChunkSize,
630 15 : static_cast<hsize_t>(nRasterYSize),
631 15 : static_cast<hsize_t>(nRasterXSize)};
632 15 : H5OFFSET_TYPE offset[3] = {
633 15 : static_cast<H5OFFSET_TYPE>(iBandChunk) *
634 15 : poGDS->m_nBandChunkSize,
635 : static_cast<H5OFFSET_TYPE>(0),
636 15 : static_cast<H5OFFSET_TYPE>(0)};
637 : herr_t status =
638 15 : H5Sselect_hyperslab(poGDS->dataspace_id, H5S_SELECT_SET,
639 : offset, nullptr, count, nullptr);
640 15 : if (status < 0)
641 0 : return CE_Failure;
642 :
643 : const hid_t memspace =
644 15 : H5Screate_simple(poGDS->ndims, count, nullptr);
645 15 : H5OFFSET_TYPE mem_offset[3] = {0, 0, 0};
646 : status =
647 15 : H5Sselect_hyperslab(memspace, H5S_SELECT_SET, mem_offset,
648 : nullptr, count, nullptr);
649 15 : if (status < 0)
650 : {
651 0 : H5Sclose(memspace);
652 0 : return CE_Failure;
653 : }
654 :
655 15 : status = H5Dread(poGDS->dataset_id, poGDS->native, memspace,
656 : poGDS->dataspace_id, H5P_DEFAULT,
657 15 : poGDS->m_abyBandChunk.data());
658 :
659 15 : H5Sclose(memspace);
660 :
661 15 : if (status < 0)
662 : {
663 0 : CPLError(
664 : CE_Failure, CPLE_AppDefined,
665 : "HDF5ImageRasterBand::IRasterIO(): H5Dread() failed");
666 0 : return CE_Failure;
667 : }
668 :
669 15 : poGDS->m_iCurrentBandChunk = iBandChunk;
670 : }
671 :
672 1447 : for (int iY = 0; iY < nYSize; ++iY)
673 : {
674 2508 : GDALCopyWords(poGDS->m_abyBandChunk.data() +
675 1254 : static_cast<size_t>((nBand - 1) %
676 1254 : poGDS->m_nBandChunkSize) *
677 1254 : nRasterYSize * nRasterXSize * nDTSize +
678 1254 : static_cast<size_t>(nYOff + iY) *
679 1254 : nRasterXSize * nDTSize +
680 1254 : nXOff * nDTSize,
681 : eDataType, nDTSize,
682 : static_cast<GByte *>(pData) +
683 1254 : static_cast<size_t>(iY) * nLineSpace,
684 : eBufType, static_cast<int>(nPixelSpace), nXSize);
685 : }
686 193 : return CE_None;
687 : }
688 : }
689 :
690 : const bool bIsExpectedLayout =
691 204 : (bIsBandInterleavedData ||
692 171 : (poGDS->ndims == 2 && poGDS->GetYIndex() == 0 &&
693 85 : poGDS->GetXIndex() == 1));
694 118 : if (eRWFlag == GF_Read && bIsExpectedLayout && nXSize == nBufXSize &&
695 116 : nYSize == nBufYSize && eBufType == eDataType &&
696 68 : nPixelSpace == nDTSize && nLineSpace == nXSize * nPixelSpace)
697 : {
698 : HDF5_GLOBAL_LOCK();
699 :
700 68 : hsize_t count[3] = {1, static_cast<hsize_t>(nYSize),
701 68 : static_cast<hsize_t>(nXSize)};
702 68 : H5OFFSET_TYPE offset[3] = {static_cast<H5OFFSET_TYPE>(nBand - 1),
703 68 : static_cast<H5OFFSET_TYPE>(nYOff),
704 68 : static_cast<H5OFFSET_TYPE>(nXOff)};
705 68 : if (poGDS->ndims == 2)
706 : {
707 47 : count[0] = count[1];
708 47 : count[1] = count[2];
709 :
710 47 : offset[0] = offset[1];
711 47 : offset[1] = offset[2];
712 : }
713 68 : herr_t status = H5Sselect_hyperslab(poGDS->dataspace_id, H5S_SELECT_SET,
714 : offset, nullptr, count, nullptr);
715 68 : if (status < 0)
716 0 : return CE_Failure;
717 :
718 68 : const hid_t memspace = H5Screate_simple(poGDS->ndims, count, nullptr);
719 68 : H5OFFSET_TYPE mem_offset[3] = {0, 0, 0};
720 68 : status = H5Sselect_hyperslab(memspace, H5S_SELECT_SET, mem_offset,
721 : nullptr, count, nullptr);
722 68 : if (status < 0)
723 : {
724 0 : H5Sclose(memspace);
725 0 : return CE_Failure;
726 : }
727 :
728 68 : status = H5Dread(poGDS->dataset_id, poGDS->native, memspace,
729 : poGDS->dataspace_id, H5P_DEFAULT, pData);
730 :
731 68 : H5Sclose(memspace);
732 :
733 68 : if (status < 0)
734 : {
735 0 : CPLError(CE_Failure, CPLE_AppDefined,
736 : "HDF5ImageRasterBand::IRasterIO(): H5Dread() failed");
737 0 : return CE_Failure;
738 : }
739 :
740 68 : return CE_None;
741 : }
742 :
743 : // If the request is still small enough, try to read from libhdf5 with
744 : // the natural interleaving into a temporary MEMDataset, and then read
745 : // from it with the requested interleaving and data type.
746 50 : if (eRWFlag == GF_Read && bIsExpectedLayout && nXSize == nBufXSize &&
747 100 : nYSize == nBufYSize &&
748 48 : static_cast<GIntBig>(nXSize) * nYSize < CPLGetUsablePhysicalRAM() / 10)
749 : {
750 : auto poMemDS = std::unique_ptr<GDALDataset>(
751 48 : MEMDataset::Create("", nXSize, nYSize, 1, eDataType, nullptr));
752 48 : if (poMemDS)
753 : {
754 48 : void *pMemData = poMemDS->GetInternalHandle("MEMORY1");
755 48 : CPLAssert(pMemData);
756 : // Read from HDF5 into the temporary MEMDataset using the
757 : // natural interleaving of the HDF5 dataset
758 48 : ++m_nIRasterIORecCounter;
759 : CPLErr eErr =
760 96 : IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pMemData,
761 : nXSize, nYSize, eDataType, nDTSize,
762 48 : static_cast<GSpacing>(nXSize) * nDTSize, psExtraArg);
763 48 : --m_nIRasterIORecCounter;
764 48 : if (eErr != CE_None)
765 : {
766 0 : return CE_Failure;
767 : }
768 : // Copy to the final buffer using requested data type and spacings.
769 48 : return poMemDS->GetRasterBand(1)->RasterIO(
770 : GF_Read, 0, 0, nXSize, nYSize, pData, nXSize, nYSize, eBufType,
771 48 : nPixelSpace, nLineSpace, nullptr);
772 : }
773 : }
774 :
775 2 : return GDALPamRasterBand::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
776 : pData, nBufXSize, nBufYSize, eBufType,
777 2 : nPixelSpace, nLineSpace, psExtraArg);
778 : }
779 :
780 : /************************************************************************/
781 : /* IRasterIO() */
782 : /************************************************************************/
783 :
784 12 : CPLErr HDF5ImageDataset::IRasterIO(
785 : GDALRWFlag eRWFlag, int nXOff, int nYOff, int nXSize, int nYSize,
786 : void *pData, int nBufXSize, int nBufYSize, GDALDataType eBufType,
787 : int nBandCount, BANDMAP_TYPE panBandMap, GSpacing nPixelSpace,
788 : GSpacing nLineSpace, GSpacing nBandSpace, GDALRasterIOExtraArg *psExtraArg)
789 :
790 : {
791 : #ifdef HDF5_HAVE_FLOAT16
792 : if (m_bConvertFromFloat16)
793 : {
794 : return HDF5Dataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize,
795 : pData, nBufXSize, nBufYSize, eBufType,
796 : nBandCount, panBandMap, nPixelSpace,
797 : nLineSpace, nBandSpace, psExtraArg);
798 : }
799 : #endif
800 :
801 16 : const auto IsConsecutiveBands = [](const int *panVals, int nCount)
802 : {
803 71 : for (int i = 1; i < nCount; ++i)
804 : {
805 57 : if (panVals[i] != panVals[i - 1] + 1)
806 2 : return false;
807 : }
808 14 : return true;
809 : };
810 :
811 12 : const auto eDT = GetRasterBand(1)->GetRasterDataType();
812 12 : const int nDTSize = GDALGetDataTypeSizeBytes(eDT);
813 :
814 : // Band-interleaved data and request
815 20 : const bool bIsBandInterleavedData = ndims == 3 && m_nOtherDimIndex == 0 &&
816 32 : GetYIndex() == 1 && GetXIndex() == 2;
817 12 : if (eRWFlag == GF_Read && bIsBandInterleavedData && nXSize == nBufXSize &&
818 8 : nYSize == nBufYSize && IsConsecutiveBands(panBandMap, nBandCount) &&
819 6 : eBufType == eDT && nPixelSpace == nDTSize &&
820 24 : nLineSpace == nXSize * nPixelSpace && nBandSpace == nYSize * nLineSpace)
821 : {
822 : HDF5_GLOBAL_LOCK();
823 :
824 5 : hsize_t count[3] = {static_cast<hsize_t>(nBandCount),
825 5 : static_cast<hsize_t>(nYSize),
826 5 : static_cast<hsize_t>(nXSize)};
827 : H5OFFSET_TYPE offset[3] = {
828 5 : static_cast<H5OFFSET_TYPE>(panBandMap[0] - 1),
829 5 : static_cast<H5OFFSET_TYPE>(nYOff),
830 5 : static_cast<H5OFFSET_TYPE>(nXOff)};
831 5 : herr_t status = H5Sselect_hyperslab(dataspace_id, H5S_SELECT_SET,
832 : offset, nullptr, count, nullptr);
833 5 : if (status < 0)
834 0 : return CE_Failure;
835 :
836 5 : const hid_t memspace = H5Screate_simple(ndims, count, nullptr);
837 5 : H5OFFSET_TYPE mem_offset[3] = {0, 0, 0};
838 5 : status = H5Sselect_hyperslab(memspace, H5S_SELECT_SET, mem_offset,
839 : nullptr, count, nullptr);
840 5 : if (status < 0)
841 : {
842 0 : H5Sclose(memspace);
843 0 : return CE_Failure;
844 : }
845 :
846 5 : status = H5Dread(dataset_id, native, memspace, dataspace_id,
847 : H5P_DEFAULT, pData);
848 :
849 5 : H5Sclose(memspace);
850 :
851 5 : if (status < 0)
852 : {
853 0 : CPLError(CE_Failure, CPLE_AppDefined,
854 : "HDF5ImageDataset::IRasterIO(): H5Dread() failed");
855 0 : return CE_Failure;
856 : }
857 :
858 5 : return CE_None;
859 : }
860 :
861 : // Pixel-interleaved data and request
862 :
863 11 : const bool bIsPixelInterleaveData = ndims == 3 && m_nOtherDimIndex == 2 &&
864 18 : GetYIndex() == 0 && GetXIndex() == 1;
865 7 : if (eRWFlag == GF_Read && bIsPixelInterleaveData && nXSize == nBufXSize &&
866 4 : nYSize == nBufYSize && IsConsecutiveBands(panBandMap, nBandCount) &&
867 4 : eBufType == eDT && nBandSpace == nDTSize &&
868 17 : nPixelSpace == nBandCount * nBandSpace &&
869 3 : nLineSpace == nXSize * nPixelSpace)
870 : {
871 : HDF5_GLOBAL_LOCK();
872 :
873 3 : hsize_t count[3] = {static_cast<hsize_t>(nYSize),
874 3 : static_cast<hsize_t>(nXSize),
875 3 : static_cast<hsize_t>(nBandCount)};
876 : H5OFFSET_TYPE offset[3] = {
877 3 : static_cast<H5OFFSET_TYPE>(nYOff),
878 3 : static_cast<H5OFFSET_TYPE>(nXOff),
879 3 : static_cast<H5OFFSET_TYPE>(panBandMap[0] - 1)};
880 3 : herr_t status = H5Sselect_hyperslab(dataspace_id, H5S_SELECT_SET,
881 : offset, nullptr, count, nullptr);
882 3 : if (status < 0)
883 0 : return CE_Failure;
884 :
885 3 : const hid_t memspace = H5Screate_simple(ndims, count, nullptr);
886 3 : H5OFFSET_TYPE mem_offset[3] = {0, 0, 0};
887 3 : status = H5Sselect_hyperslab(memspace, H5S_SELECT_SET, mem_offset,
888 : nullptr, count, nullptr);
889 3 : if (status < 0)
890 : {
891 0 : H5Sclose(memspace);
892 0 : return CE_Failure;
893 : }
894 :
895 3 : status = H5Dread(dataset_id, native, memspace, dataspace_id,
896 : H5P_DEFAULT, pData);
897 :
898 3 : H5Sclose(memspace);
899 :
900 3 : if (status < 0)
901 : {
902 0 : CPLError(CE_Failure, CPLE_AppDefined,
903 : "HDF5ImageDataset::IRasterIO(): H5Dread() failed");
904 0 : return CE_Failure;
905 : }
906 :
907 3 : return CE_None;
908 : }
909 :
910 : // If the request is still small enough, try to read from libhdf5 with
911 : // the natural interleaving into a temporary MEMDataset, and then read
912 : // from it with the requested interleaving and data type.
913 4 : if (eRWFlag == GF_Read &&
914 4 : (bIsBandInterleavedData || bIsPixelInterleaveData) &&
915 8 : nXSize == nBufXSize && nYSize == nBufYSize &&
916 12 : IsConsecutiveBands(panBandMap, nBandCount) &&
917 3 : static_cast<GIntBig>(nXSize) * nYSize <
918 3 : CPLGetUsablePhysicalRAM() / 10 / nBandCount)
919 : {
920 3 : const char *const apszOptions[] = {
921 3 : bIsPixelInterleaveData ? "INTERLEAVE=PIXEL" : nullptr, nullptr};
922 : auto poMemDS = std::unique_ptr<GDALDataset>(
923 3 : MEMDataset::Create("", nXSize, nYSize, nBandCount, eDT,
924 3 : const_cast<char **>(apszOptions)));
925 3 : if (poMemDS)
926 : {
927 3 : void *pMemData = poMemDS->GetInternalHandle("MEMORY1");
928 3 : CPLAssert(pMemData);
929 : // Read from HDF5 into the temporary MEMDataset using the
930 : // natural interleaving of the HDF5 dataset
931 9 : if (IRasterIO(
932 : eRWFlag, nXOff, nYOff, nXSize, nYSize, pMemData, nXSize,
933 : nYSize, eDT, nBandCount, panBandMap,
934 1 : bIsBandInterleavedData ? nDTSize : nDTSize * nBandCount,
935 : bIsBandInterleavedData
936 2 : ? static_cast<GSpacing>(nXSize) * nDTSize
937 1 : : static_cast<GSpacing>(nXSize) * nDTSize * nBandCount,
938 : bIsBandInterleavedData
939 2 : ? static_cast<GSpacing>(nYSize) * nXSize * nDTSize
940 : : nDTSize,
941 3 : psExtraArg) != CE_None)
942 : {
943 0 : return CE_Failure;
944 : }
945 : // Copy to the final buffer using requested data type and spacings.
946 3 : return poMemDS->RasterIO(GF_Read, 0, 0, nXSize, nYSize, pData,
947 : nXSize, nYSize, eBufType, nBandCount,
948 : nullptr, nPixelSpace, nLineSpace,
949 3 : nBandSpace, nullptr);
950 : }
951 : }
952 :
953 1 : return HDF5Dataset::IRasterIO(eRWFlag, nXOff, nYOff, nXSize, nYSize, pData,
954 : nBufXSize, nBufYSize, eBufType, nBandCount,
955 : panBandMap, nPixelSpace, nLineSpace,
956 1 : nBandSpace, psExtraArg);
957 : }
958 :
959 : /************************************************************************/
960 : /* Open() */
961 : /************************************************************************/
962 60 : GDALDataset *HDF5ImageDataset::Open(GDALOpenInfo *poOpenInfo)
963 : {
964 60 : if (!STARTS_WITH_CI(poOpenInfo->pszFilename, "HDF5:"))
965 0 : return nullptr;
966 :
967 : HDF5_GLOBAL_LOCK();
968 :
969 : // Confirm the requested access is supported.
970 60 : if (poOpenInfo->eAccess == GA_Update)
971 : {
972 0 : ReportUpdateNotSupportedByDriver("HDF5");
973 0 : return nullptr;
974 : }
975 :
976 60 : HDF5ImageDataset *poDS = new HDF5ImageDataset();
977 :
978 : // Create a corresponding GDALDataset.
979 : char **papszName =
980 60 : CSLTokenizeString2(poOpenInfo->pszFilename, ":",
981 : CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES);
982 :
983 60 : if (!(CSLCount(papszName) == 3 || CSLCount(papszName) == 4))
984 : {
985 0 : CSLDestroy(papszName);
986 0 : delete poDS;
987 0 : return nullptr;
988 : }
989 :
990 60 : poDS->SetDescription(poOpenInfo->pszFilename);
991 :
992 : // Check for drive name in windows HDF5:"D:\...
993 120 : CPLString osSubdatasetName;
994 :
995 120 : CPLString osFilename(papszName[1]);
996 :
997 60 : if ((strlen(papszName[1]) == 1 && papszName[3] != nullptr) ||
998 60 : (STARTS_WITH(papszName[1], "/vsicurl/http") && papszName[3] != nullptr))
999 : {
1000 0 : osFilename += ":";
1001 0 : osFilename += papszName[2];
1002 0 : osSubdatasetName = papszName[3];
1003 : }
1004 : else
1005 : {
1006 60 : osSubdatasetName = papszName[2];
1007 : }
1008 :
1009 60 : poDS->SetSubdatasetName(osSubdatasetName);
1010 :
1011 60 : CSLDestroy(papszName);
1012 60 : papszName = nullptr;
1013 :
1014 60 : poDS->SetPhysicalFilename(osFilename);
1015 :
1016 : // Try opening the dataset.
1017 60 : poDS->m_hHDF5 = GDAL_HDF5Open(osFilename);
1018 60 : if (poDS->m_hHDF5 < 0)
1019 : {
1020 0 : delete poDS;
1021 0 : return nullptr;
1022 : }
1023 :
1024 60 : poDS->hGroupID = H5Gopen(poDS->m_hHDF5, "/");
1025 60 : if (poDS->hGroupID < 0)
1026 : {
1027 0 : delete poDS;
1028 0 : return nullptr;
1029 : }
1030 :
1031 : // This is an HDF5 file.
1032 60 : poDS->ReadGlobalAttributes(FALSE);
1033 :
1034 : // Create HDF5 Data Hierarchy in a link list.
1035 60 : poDS->poH5Objects = poDS->HDF5FindDatasetObjectsbyPath(poDS->poH5RootGroup,
1036 : osSubdatasetName);
1037 :
1038 60 : if (poDS->poH5Objects == nullptr)
1039 : {
1040 0 : delete poDS;
1041 0 : return nullptr;
1042 : }
1043 :
1044 : // Retrieve HDF5 data information.
1045 60 : poDS->dataset_id = H5Dopen(poDS->m_hHDF5, poDS->poH5Objects->pszPath);
1046 60 : poDS->dataspace_id = H5Dget_space(poDS->dataset_id);
1047 60 : poDS->ndims = H5Sget_simple_extent_ndims(poDS->dataspace_id);
1048 60 : if (poDS->ndims <= 0)
1049 : {
1050 0 : delete poDS;
1051 0 : return nullptr;
1052 : }
1053 60 : poDS->dims =
1054 60 : static_cast<hsize_t *>(CPLCalloc(poDS->ndims, sizeof(hsize_t)));
1055 60 : poDS->maxdims =
1056 60 : static_cast<hsize_t *>(CPLCalloc(poDS->ndims, sizeof(hsize_t)));
1057 60 : poDS->dimensions = H5Sget_simple_extent_dims(poDS->dataspace_id, poDS->dims,
1058 : poDS->maxdims);
1059 192 : for (int i = 0; i < poDS->dimensions; ++i)
1060 : {
1061 266 : if (poDS->dims[i] >
1062 133 : static_cast<hsize_t>(std::numeric_limits<int>::max()))
1063 : {
1064 1 : CPLError(CE_Failure, CPLE_NotSupported,
1065 : "At least one dimension size exceeds INT_MAX !");
1066 1 : delete poDS;
1067 1 : return nullptr;
1068 : }
1069 : }
1070 :
1071 59 : auto datatype = H5Dget_type(poDS->dataset_id);
1072 59 : poDS->native = H5Tget_native_type(datatype, H5T_DIR_ASCEND);
1073 59 : H5Tclose(datatype);
1074 :
1075 59 : const auto eGDALDataType = poDS->GetDataType(poDS->native);
1076 59 : if (eGDALDataType == GDT_Unknown)
1077 : {
1078 0 : CPLError(CE_Failure, CPLE_AppDefined, "Unhandled HDF5 data type");
1079 0 : delete poDS;
1080 0 : return nullptr;
1081 : }
1082 :
1083 : #ifdef HDF5_HAVE_FLOAT16
1084 : if (H5Tequal(H5T_NATIVE_FLOAT16, poDS->native) ||
1085 : IsNativeCFloat16(poDS->native))
1086 : {
1087 : poDS->m_bConvertFromFloat16 = true;
1088 : }
1089 : #endif
1090 :
1091 : // CSK code in IdentifyProductType() and CreateProjections()
1092 : // uses dataset metadata.
1093 59 : poDS->SetMetadata(poDS->m_aosMetadata.List());
1094 :
1095 : // Check if the hdf5 is a well known product type
1096 59 : poDS->IdentifyProductType();
1097 :
1098 59 : poDS->m_nYIndex = poDS->IsComplexCSKL1A() ? 0 : poDS->ndims - 2;
1099 59 : poDS->m_nXIndex = poDS->IsComplexCSKL1A() ? 1 : poDS->ndims - 1;
1100 :
1101 59 : if (poDS->IsComplexCSKL1A())
1102 : {
1103 0 : poDS->m_nOtherDimIndex = 2;
1104 : }
1105 59 : else if (poDS->ndims == 3)
1106 : {
1107 15 : poDS->m_nOtherDimIndex = 0;
1108 : }
1109 :
1110 59 : if (HDF5EOSParser::HasHDFEOS(poDS->hGroupID))
1111 : {
1112 38 : HDF5EOSParser oHDFEOSParser;
1113 19 : if (oHDFEOSParser.Parse(poDS->hGroupID))
1114 : {
1115 19 : CPLDebug("HDF5", "Successfully parsed HDFEOS metadata");
1116 38 : HDF5EOSParser::GridDataFieldMetadata oGridDataFieldMetadata;
1117 38 : HDF5EOSParser::SwathDataFieldMetadata oSwathDataFieldMetadata;
1118 19 : if (oHDFEOSParser.GetDataModel() ==
1119 6 : HDF5EOSParser::DataModel::GRID &&
1120 6 : oHDFEOSParser.GetGridDataFieldMetadata(
1121 25 : osSubdatasetName.c_str(), oGridDataFieldMetadata) &&
1122 6 : static_cast<int>(oGridDataFieldMetadata.aoDimensions.size()) ==
1123 6 : poDS->ndims)
1124 : {
1125 6 : int iDim = 0;
1126 20 : for (const auto &oDim : oGridDataFieldMetadata.aoDimensions)
1127 : {
1128 14 : if (oDim.osName == "XDim")
1129 6 : poDS->m_nXIndex = iDim;
1130 8 : else if (oDim.osName == "YDim")
1131 6 : poDS->m_nYIndex = iDim;
1132 : else
1133 2 : poDS->m_nOtherDimIndex = iDim;
1134 14 : ++iDim;
1135 : }
1136 :
1137 6 : if (oGridDataFieldMetadata.poGridMetadata->GetGeoTransform(
1138 6 : poDS->m_gt))
1139 6 : poDS->bHasGeoTransform = true;
1140 :
1141 12 : auto poSRS = oGridDataFieldMetadata.poGridMetadata->GetSRS();
1142 6 : if (poSRS)
1143 6 : poDS->m_oSRS = *(poSRS.get());
1144 : }
1145 13 : else if (oHDFEOSParser.GetDataModel() ==
1146 13 : HDF5EOSParser::DataModel::SWATH &&
1147 13 : oHDFEOSParser.GetSwathDataFieldMetadata(
1148 7 : osSubdatasetName.c_str(), oSwathDataFieldMetadata) &&
1149 : static_cast<int>(
1150 7 : oSwathDataFieldMetadata.aoDimensions.size()) ==
1151 7 : poDS->ndims &&
1152 33 : oSwathDataFieldMetadata.iXDim >= 0 &&
1153 7 : oSwathDataFieldMetadata.iYDim >= 0)
1154 : {
1155 7 : poDS->m_nXIndex = oSwathDataFieldMetadata.iXDim;
1156 7 : poDS->m_nYIndex = oSwathDataFieldMetadata.iYDim;
1157 7 : poDS->m_nOtherDimIndex = oSwathDataFieldMetadata.iOtherDim;
1158 7 : if (!oSwathDataFieldMetadata.osLongitudeSubdataset.empty())
1159 : {
1160 14 : CPLStringList aosGeolocation;
1161 :
1162 : // Arbitrary
1163 7 : aosGeolocation.AddNameValue("SRS", SRS_WKT_WGS84_LAT_LONG);
1164 : aosGeolocation.AddNameValue(
1165 : "X_DATASET",
1166 14 : ("HDF5:\"" + osFilename +
1167 14 : "\":" + oSwathDataFieldMetadata.osLongitudeSubdataset)
1168 7 : .c_str());
1169 7 : aosGeolocation.AddNameValue("X_BAND", "1");
1170 : aosGeolocation.AddNameValue(
1171 : "Y_DATASET",
1172 14 : ("HDF5:\"" + osFilename +
1173 14 : "\":" + oSwathDataFieldMetadata.osLatitudeSubdataset)
1174 7 : .c_str());
1175 7 : aosGeolocation.AddNameValue("Y_BAND", "1");
1176 : aosGeolocation.AddNameValue(
1177 : "PIXEL_OFFSET",
1178 7 : CPLSPrintf("%d", oSwathDataFieldMetadata.nPixelOffset));
1179 : aosGeolocation.AddNameValue(
1180 : "PIXEL_STEP",
1181 7 : CPLSPrintf("%d", oSwathDataFieldMetadata.nPixelStep));
1182 : aosGeolocation.AddNameValue(
1183 : "LINE_OFFSET",
1184 7 : CPLSPrintf("%d", oSwathDataFieldMetadata.nLineOffset));
1185 : aosGeolocation.AddNameValue(
1186 : "LINE_STEP",
1187 7 : CPLSPrintf("%d", oSwathDataFieldMetadata.nLineStep));
1188 : // Not totally sure about that
1189 : aosGeolocation.AddNameValue("GEOREFERENCING_CONVENTION",
1190 7 : "PIXEL_CENTER");
1191 7 : poDS->SetMetadata(aosGeolocation.List(), "GEOLOCATION");
1192 : }
1193 : }
1194 : }
1195 : }
1196 :
1197 59 : poDS->nRasterYSize =
1198 59 : poDS->GetYIndex() < 0
1199 59 : ? 1
1200 58 : : static_cast<int>(poDS->dims[poDS->GetYIndex()]); // nRows
1201 59 : poDS->nRasterXSize =
1202 59 : static_cast<int>(poDS->dims[poDS->GetXIndex()]); // nCols
1203 59 : int nBands = 1;
1204 59 : if (poDS->m_nOtherDimIndex >= 0)
1205 : {
1206 15 : nBands = static_cast<int>(poDS->dims[poDS->m_nOtherDimIndex]);
1207 : }
1208 :
1209 118 : CPLStringList aosMetadata;
1210 59 : std::map<std::string, CPLStringList> oMapBandSpecificMetadata;
1211 59 : if (poDS->poH5Objects->nType == H5G_DATASET)
1212 : {
1213 59 : HDF5Dataset::CreateMetadata(poDS->m_hHDF5, poDS->poH5Objects,
1214 : H5G_DATASET, false, aosMetadata);
1215 59 : if (nBands > 1 && poDS->nRasterXSize != nBands &&
1216 14 : poDS->nRasterYSize != nBands)
1217 : {
1218 : // Heuristics to detect non-scalar attributes, that are intended
1219 : // to be attached to a specific band.
1220 28 : const CPLStringList aosMetadataDup(aosMetadata);
1221 52 : for (const auto &[pszKey, pszValue] :
1222 66 : cpl::IterateNameValue(aosMetadataDup))
1223 : {
1224 26 : const hid_t hAttrID = H5Aopen_name(poDS->dataset_id, pszKey);
1225 26 : const hid_t hAttrSpace = H5Aget_space(hAttrID);
1226 47 : if (H5Sget_simple_extent_ndims(hAttrSpace) == 1 &&
1227 21 : H5Sget_simple_extent_npoints(hAttrSpace) == nBands)
1228 : {
1229 : CPLStringList aosTokens(
1230 40 : CSLTokenizeString2(pszValue, " ", 0));
1231 20 : if (aosTokens.size() == nBands)
1232 : {
1233 40 : std::string osAttrName(pszKey);
1234 30 : if (osAttrName.size() > strlen("_coefficients") &&
1235 30 : osAttrName.substr(osAttrName.size() -
1236 10 : strlen("_coefficients")) ==
1237 : "_coefficients")
1238 : {
1239 5 : osAttrName.pop_back();
1240 : }
1241 25 : else if (osAttrName.size() > strlen("_wavelengths") &&
1242 25 : osAttrName.substr(osAttrName.size() -
1243 10 : strlen("_wavelengths")) ==
1244 : "_wavelengths")
1245 : {
1246 5 : osAttrName.pop_back();
1247 : }
1248 15 : else if (osAttrName.size() > strlen("_list") &&
1249 15 : osAttrName.substr(osAttrName.size() -
1250 5 : strlen("_list")) == "_list")
1251 : {
1252 5 : osAttrName.resize(osAttrName.size() -
1253 : strlen("_list"));
1254 : }
1255 20 : oMapBandSpecificMetadata[osAttrName] =
1256 40 : std::move(aosTokens);
1257 20 : aosMetadata.SetNameValue(pszKey, nullptr);
1258 : }
1259 : }
1260 26 : H5Sclose(hAttrSpace);
1261 26 : H5Aclose(hAttrID);
1262 : }
1263 : }
1264 : }
1265 :
1266 59 : poDS->m_nBlockXSize = poDS->GetRasterXSize();
1267 59 : poDS->m_nBlockYSize = 1;
1268 59 : poDS->m_nBandChunkSize = 1;
1269 :
1270 : // Check for chunksize and set it as the blocksize (optimizes read).
1271 59 : const hid_t listid = H5Dget_create_plist(poDS->dataset_id);
1272 59 : if (listid > 0)
1273 : {
1274 59 : if (H5Pget_layout(listid) == H5D_CHUNKED)
1275 : {
1276 12 : hsize_t panChunkDims[3] = {0, 0, 0};
1277 12 : const int nDimSize = H5Pget_chunk(listid, 3, panChunkDims);
1278 12 : CPL_IGNORE_RET_VAL(nDimSize);
1279 12 : CPLAssert(nDimSize == poDS->ndims);
1280 12 : poDS->m_nBlockXSize =
1281 12 : static_cast<int>(panChunkDims[poDS->GetXIndex()]);
1282 12 : if (poDS->GetYIndex() >= 0)
1283 12 : poDS->m_nBlockYSize =
1284 12 : static_cast<int>(panChunkDims[poDS->GetYIndex()]);
1285 12 : if (nBands > 1)
1286 : {
1287 3 : poDS->m_nBandChunkSize =
1288 3 : static_cast<int>(panChunkDims[poDS->m_nOtherDimIndex]);
1289 :
1290 3 : if (poDS->m_nBandChunkSize > 1)
1291 3 : poDS->SetMetadataItem("INTERLEAVE", "PIXEL",
1292 3 : "IMAGE_STRUCTURE");
1293 :
1294 3 : poDS->SetMetadataItem("BAND_CHUNK_SIZE",
1295 : CPLSPrintf("%d", poDS->m_nBandChunkSize),
1296 3 : "IMAGE_STRUCTURE");
1297 : }
1298 : }
1299 :
1300 59 : const int nFilters = H5Pget_nfilters(listid);
1301 65 : for (int i = 0; i < nFilters; ++i)
1302 : {
1303 6 : unsigned int flags = 0;
1304 6 : size_t cd_nelmts = 0;
1305 6 : char szName[64 + 1] = {0};
1306 6 : const auto eFilter = H5Pget_filter(listid, i, &flags, &cd_nelmts,
1307 : nullptr, 64, szName);
1308 6 : if (eFilter == H5Z_FILTER_DEFLATE)
1309 : {
1310 5 : poDS->SetMetadataItem("COMPRESSION", "DEFLATE",
1311 5 : "IMAGE_STRUCTURE");
1312 : }
1313 1 : else if (eFilter == H5Z_FILTER_SZIP)
1314 : {
1315 0 : poDS->SetMetadataItem("COMPRESSION", "SZIP", "IMAGE_STRUCTURE");
1316 : }
1317 : }
1318 :
1319 59 : H5Pclose(listid);
1320 : }
1321 :
1322 185 : for (int i = 0; i < nBands; i++)
1323 : {
1324 : HDF5ImageRasterBand *const poBand =
1325 126 : new HDF5ImageRasterBand(poDS, i + 1, eGDALDataType);
1326 :
1327 126 : poDS->SetBand(i + 1, poBand);
1328 :
1329 126 : if (poDS->poH5Objects->nType == H5G_DATASET)
1330 : {
1331 126 : poBand->SetMetadata(aosMetadata.List());
1332 166 : for (const auto &oIter : oMapBandSpecificMetadata)
1333 : {
1334 40 : poBand->SetMetadataItem(oIter.first.c_str(), oIter.second[i]);
1335 : }
1336 : }
1337 : }
1338 :
1339 59 : if (!poDS->GetMetadata("GEOLOCATION"))
1340 52 : poDS->CreateProjections();
1341 :
1342 : // Setup/check for pam .aux.xml.
1343 59 : poDS->TryLoadXML();
1344 :
1345 : // If the PAM .aux.xml file contains the serialized GEOLOCATION metadata
1346 : // domain, make sure to patch the X_DATASET and Y_DATASET values, when
1347 : // they point to the current file, to use the filename with which we have
1348 : // opened the dataset. Helps in scenarios where the .aux.xml file has
1349 : // been saved with a relative filename for example.
1350 : // Or scenarios like https://github.com/OSGeo/gdal/issues/12824 mixing
1351 : // native Windows and WSL use.
1352 : CSLConstList papszGeoLocationAfterLoadXML =
1353 59 : poDS->GetMetadata("GEOLOCATION");
1354 59 : if (papszGeoLocationAfterLoadXML)
1355 : {
1356 21 : for (const char *pszItem : {"X_DATASET", "Y_DATASET"})
1357 : {
1358 : const char *pszSubdataset =
1359 14 : CSLFetchNameValue(papszGeoLocationAfterLoadXML, pszItem);
1360 28 : if (pszSubdataset && STARTS_WITH(pszSubdataset, "HDF5:\"") &&
1361 14 : strstr(pszSubdataset, CPLGetFilename(osFilename.c_str())))
1362 : {
1363 14 : auto hSubDSInfo = GDALGetSubdatasetInfo(pszSubdataset);
1364 14 : if (hSubDSInfo)
1365 : {
1366 : char *pszOriPath =
1367 14 : GDALSubdatasetInfoGetPathComponent(hSubDSInfo);
1368 14 : if (EQUAL(CPLGetFilename(pszOriPath),
1369 : CPLGetFilename(osFilename.c_str())))
1370 : {
1371 14 : char *pszNewVal = GDALSubdatasetInfoModifyPathComponent(
1372 : hSubDSInfo, osFilename.c_str());
1373 14 : poDS->SetMetadataItem(pszItem, pszNewVal,
1374 14 : "GEOLOCATION");
1375 14 : CPLFree(pszNewVal);
1376 : }
1377 14 : CPLFree(pszOriPath);
1378 14 : GDALDestroySubdatasetInfo(hSubDSInfo);
1379 : }
1380 : }
1381 : }
1382 : }
1383 :
1384 59 : poDS->SetPamFlags(poDS->GetPamFlags() & ~GPF_DIRTY);
1385 :
1386 : // Setup overviews.
1387 59 : poDS->oOvManager.Initialize(poDS, ":::VIRTUAL:::");
1388 :
1389 59 : return poDS;
1390 : }
1391 :
1392 : /************************************************************************/
1393 : /* HDF5ImageDatasetDriverUnload() */
1394 : /************************************************************************/
1395 :
1396 6 : static void HDF5ImageDatasetDriverUnload(GDALDriver *)
1397 : {
1398 6 : HDF5UnloadFileDriver();
1399 6 : }
1400 :
1401 : /************************************************************************/
1402 : /* GDALRegister_HDF5Image() */
1403 : /************************************************************************/
1404 11 : void GDALRegister_HDF5Image()
1405 :
1406 : {
1407 11 : if (!GDAL_CHECK_VERSION("HDF5Image driver"))
1408 0 : return;
1409 :
1410 11 : if (GDALGetDriverByName(HDF5_IMAGE_DRIVER_NAME) != nullptr)
1411 0 : return;
1412 :
1413 11 : GDALDriver *poDriver = new GDALDriver();
1414 :
1415 11 : HDF5ImageDriverSetCommonMetadata(poDriver);
1416 :
1417 11 : poDriver->pfnOpen = HDF5ImageDataset::Open;
1418 11 : poDriver->pfnUnloadDriver = HDF5ImageDatasetDriverUnload;
1419 :
1420 11 : GetGDALDriverManager()->RegisterDriver(poDriver);
1421 : }
1422 :
1423 : /************************************************************************/
1424 : /* CreateODIMH5Projection() */
1425 : /************************************************************************/
1426 :
1427 : // Reference:
1428 : // http://www.knmi.nl/opera/opera3/OPERA_2008_03_WP2.1b_ODIM_H5_v2.1.pdf
1429 : //
1430 : // 4.3.2 where for geographically referenced image Groups
1431 : // We don't use the where_xscale and where_yscale parameters, but recompute them
1432 : // from the lower-left and upper-right coordinates. There's some difference.
1433 : // As all those parameters are linked together, I'm not sure which one should be
1434 : // considered as the reference.
1435 :
1436 0 : CPLErr HDF5ImageDataset::CreateODIMH5Projection()
1437 : {
1438 0 : const char *const pszProj4String = GetMetadataItem("where_projdef");
1439 0 : const char *const pszLL_lon = GetMetadataItem("where_LL_lon");
1440 0 : const char *const pszLL_lat = GetMetadataItem("where_LL_lat");
1441 0 : const char *const pszUR_lon = GetMetadataItem("where_UR_lon");
1442 0 : const char *const pszUR_lat = GetMetadataItem("where_UR_lat");
1443 0 : if (pszProj4String == nullptr || pszLL_lon == nullptr ||
1444 0 : pszLL_lat == nullptr || pszUR_lon == nullptr || pszUR_lat == nullptr)
1445 0 : return CE_Failure;
1446 :
1447 0 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1448 0 : if (m_oSRS.importFromProj4(pszProj4String) != OGRERR_NONE)
1449 0 : return CE_Failure;
1450 :
1451 0 : OGRSpatialReference oSRSWGS84;
1452 0 : oSRSWGS84.SetWellKnownGeogCS("WGS84");
1453 0 : oSRSWGS84.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
1454 :
1455 : OGRCoordinateTransformation *poCT =
1456 0 : OGRCreateCoordinateTransformation(&oSRSWGS84, &m_oSRS);
1457 0 : if (poCT == nullptr)
1458 0 : return CE_Failure;
1459 :
1460 : // Reproject corners from long,lat WGS84 to the target SRS.
1461 0 : double dfLLX = CPLAtof(pszLL_lon);
1462 0 : double dfLLY = CPLAtof(pszLL_lat);
1463 0 : double dfURX = CPLAtof(pszUR_lon);
1464 0 : double dfURY = CPLAtof(pszUR_lat);
1465 0 : if (!poCT->Transform(1, &dfLLX, &dfLLY) ||
1466 0 : !poCT->Transform(1, &dfURX, &dfURY))
1467 : {
1468 0 : delete poCT;
1469 0 : return CE_Failure;
1470 : }
1471 0 : delete poCT;
1472 :
1473 : // Compute the geotransform now.
1474 0 : const double dfPixelX = (dfURX - dfLLX) / nRasterXSize;
1475 0 : const double dfPixelY = (dfURY - dfLLY) / nRasterYSize;
1476 :
1477 0 : bHasGeoTransform = true;
1478 0 : m_gt[0] = dfLLX;
1479 0 : m_gt[1] = dfPixelX;
1480 0 : m_gt[2] = 0;
1481 0 : m_gt[3] = dfURY;
1482 0 : m_gt[4] = 0;
1483 0 : m_gt[5] = -dfPixelY;
1484 :
1485 0 : return CE_None;
1486 : }
1487 :
1488 : /************************************************************************/
1489 : /* CreateProjections() */
1490 : /************************************************************************/
1491 52 : CPLErr HDF5ImageDataset::CreateProjections()
1492 : {
1493 52 : switch (iSubdatasetType)
1494 : {
1495 2 : case CSK_PRODUCT:
1496 : {
1497 2 : int productType = PROD_UNKNOWN;
1498 :
1499 2 : if (GetMetadataItem("Product_Type") != nullptr)
1500 : {
1501 : // Get the format's level.
1502 : const char *osMissionLevel =
1503 2 : HDF5Dataset::GetMetadataItem("Product_Type");
1504 :
1505 2 : if (STARTS_WITH_CI(osMissionLevel, "RAW"))
1506 0 : productType = PROD_CSK_L0;
1507 :
1508 2 : if (STARTS_WITH_CI(osMissionLevel, "SSC"))
1509 0 : productType = PROD_CSK_L1A;
1510 :
1511 2 : if (STARTS_WITH_CI(osMissionLevel, "DGM"))
1512 1 : productType = PROD_CSK_L1B;
1513 :
1514 2 : if (STARTS_WITH_CI(osMissionLevel, "GEC"))
1515 1 : productType = PROD_CSK_L1C;
1516 :
1517 2 : if (STARTS_WITH_CI(osMissionLevel, "GTC"))
1518 0 : productType = PROD_CSK_L1D;
1519 : }
1520 :
1521 2 : CaptureCSKGeoTransform(productType);
1522 2 : CaptureCSKGeolocation(productType);
1523 2 : CaptureCSKGCPs(productType);
1524 :
1525 2 : break;
1526 : }
1527 50 : case UNKNOWN_PRODUCT:
1528 : {
1529 50 : constexpr int NBGCPLAT = 100;
1530 50 : constexpr int NBGCPLON = 30;
1531 :
1532 50 : const int nDeltaLat = nRasterYSize / NBGCPLAT;
1533 50 : const int nDeltaLon = nRasterXSize / NBGCPLON;
1534 :
1535 50 : if (nDeltaLat == 0 || nDeltaLon == 0)
1536 42 : return CE_None;
1537 :
1538 : // Create HDF5 Data Hierarchy in a link list.
1539 8 : poH5Objects = HDF5FindDatasetObjects(poH5RootGroup, "Latitude");
1540 8 : if (!poH5Objects)
1541 : {
1542 8 : if (GetMetadataItem("where_projdef") != nullptr)
1543 0 : return CreateODIMH5Projection();
1544 8 : return CE_None;
1545 : }
1546 :
1547 : // The Latitude and Longitude arrays must have a rank of 2 to
1548 : // retrieve GCPs.
1549 0 : if (poH5Objects->nRank != 2 ||
1550 0 : poH5Objects->paDims[0] != static_cast<size_t>(nRasterYSize) ||
1551 0 : poH5Objects->paDims[1] != static_cast<size_t>(nRasterXSize))
1552 : {
1553 0 : return CE_None;
1554 : }
1555 :
1556 : // Retrieve HDF5 data information.
1557 : const hid_t LatitudeDatasetID =
1558 0 : H5Dopen(m_hHDF5, poH5Objects->pszPath);
1559 : // LatitudeDataspaceID = H5Dget_space(dataset_id);
1560 :
1561 0 : poH5Objects = HDF5FindDatasetObjects(poH5RootGroup, "Longitude");
1562 : // GCPs.
1563 0 : if (poH5Objects == nullptr || poH5Objects->nRank != 2 ||
1564 0 : poH5Objects->paDims[0] != static_cast<size_t>(nRasterYSize) ||
1565 0 : poH5Objects->paDims[1] != static_cast<size_t>(nRasterXSize))
1566 : {
1567 0 : if (LatitudeDatasetID > 0)
1568 0 : H5Dclose(LatitudeDatasetID);
1569 0 : return CE_None;
1570 : }
1571 :
1572 : const hid_t LongitudeDatasetID =
1573 0 : H5Dopen(m_hHDF5, poH5Objects->pszPath);
1574 : // LongitudeDataspaceID = H5Dget_space(dataset_id);
1575 :
1576 0 : if (LatitudeDatasetID > 0 && LongitudeDatasetID > 0)
1577 : {
1578 : float *const Latitude =
1579 0 : static_cast<float *>(VSI_MALLOC3_VERBOSE(
1580 : nRasterYSize, nRasterXSize, sizeof(float)));
1581 : float *const Longitude =
1582 0 : static_cast<float *>(VSI_MALLOC3_VERBOSE(
1583 : nRasterYSize, nRasterXSize, sizeof(float)));
1584 0 : if (!Latitude || !Longitude)
1585 : {
1586 0 : CPLFree(Latitude);
1587 0 : CPLFree(Longitude);
1588 0 : H5Dclose(LatitudeDatasetID);
1589 0 : H5Dclose(LongitudeDatasetID);
1590 0 : return CE_Failure;
1591 : }
1592 0 : memset(Latitude, 0,
1593 0 : static_cast<size_t>(nRasterXSize) * nRasterYSize *
1594 : sizeof(float));
1595 0 : memset(Longitude, 0,
1596 0 : static_cast<size_t>(nRasterXSize) * nRasterYSize *
1597 : sizeof(float));
1598 :
1599 : // netCDF convention for nodata
1600 0 : double dfLatNoData = 0;
1601 0 : bool bHasLatNoData = GH5_FetchAttribute(
1602 : LatitudeDatasetID, "_FillValue", dfLatNoData);
1603 :
1604 0 : double dfLongNoData = 0;
1605 0 : bool bHasLongNoData = GH5_FetchAttribute(
1606 : LongitudeDatasetID, "_FillValue", dfLongNoData);
1607 :
1608 0 : H5Dread(LatitudeDatasetID, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL,
1609 : H5P_DEFAULT, Latitude);
1610 :
1611 0 : H5Dread(LongitudeDatasetID, H5T_NATIVE_FLOAT, H5S_ALL, H5S_ALL,
1612 : H5P_DEFAULT, Longitude);
1613 :
1614 0 : m_oSRS.Clear();
1615 0 : m_oGCPSRS.SetWellKnownGeogCS("WGS84");
1616 :
1617 0 : const int nYLimit =
1618 0 : (static_cast<int>(nRasterYSize) / nDeltaLat) * nDeltaLat;
1619 0 : const int nXLimit =
1620 0 : (static_cast<int>(nRasterXSize) / nDeltaLon) * nDeltaLon;
1621 :
1622 : // The original code in
1623 : // https://trac.osgeo.org/gdal/changeset/8066 always add +180 to
1624 : // the longitudes, but without justification I suspect this
1625 : // might be due to handling products crossing the antimeridian.
1626 : // Trying to do it just when needed through a heuristics.
1627 0 : bool bHasLonNearMinus180 = false;
1628 0 : bool bHasLonNearPlus180 = false;
1629 0 : bool bHasLonNearZero = false;
1630 0 : for (int j = 0; j < nYLimit; j += nDeltaLat)
1631 : {
1632 0 : for (int i = 0; i < nXLimit; i += nDeltaLon)
1633 : {
1634 0 : const int iGCP = j * nRasterXSize + i;
1635 0 : if ((bHasLatNoData && static_cast<float>(dfLatNoData) ==
1636 0 : Latitude[iGCP]) ||
1637 0 : (bHasLongNoData &&
1638 0 : static_cast<float>(dfLongNoData) ==
1639 0 : Longitude[iGCP]))
1640 0 : continue;
1641 0 : if (Longitude[iGCP] > 170 && Longitude[iGCP] <= 180)
1642 0 : bHasLonNearPlus180 = true;
1643 0 : if (Longitude[iGCP] < -170 && Longitude[iGCP] >= -180)
1644 0 : bHasLonNearMinus180 = true;
1645 0 : if (fabs(Longitude[iGCP]) < 90)
1646 0 : bHasLonNearZero = true;
1647 : }
1648 : }
1649 :
1650 : // Fill the GCPs list.
1651 : const char *pszShiftGCP =
1652 0 : CPLGetConfigOption("HDF5_SHIFT_GCPX_BY_180", nullptr);
1653 : const bool bAdd180 =
1654 0 : (bHasLonNearPlus180 && bHasLonNearMinus180 &&
1655 0 : !bHasLonNearZero && pszShiftGCP == nullptr) ||
1656 0 : (pszShiftGCP != nullptr && CPLTestBool(pszShiftGCP));
1657 :
1658 0 : for (int j = 0; j < nYLimit; j += nDeltaLat)
1659 : {
1660 0 : for (int i = 0; i < nXLimit; i += nDeltaLon)
1661 : {
1662 0 : const int iGCP = j * nRasterXSize + i;
1663 0 : if ((bHasLatNoData && static_cast<float>(dfLatNoData) ==
1664 0 : Latitude[iGCP]) ||
1665 0 : (bHasLongNoData &&
1666 0 : static_cast<float>(dfLongNoData) ==
1667 0 : Longitude[iGCP]))
1668 0 : continue;
1669 0 : double dfGCPX = static_cast<double>(Longitude[iGCP]);
1670 0 : if (bAdd180)
1671 0 : dfGCPX += 180.0;
1672 0 : const double dfGCPY =
1673 0 : static_cast<double>(Latitude[iGCP]);
1674 :
1675 0 : m_aoGCPs.emplace_back("", "", i + 0.5, j + 0.5, dfGCPX,
1676 0 : dfGCPY);
1677 : }
1678 : }
1679 :
1680 0 : CPLFree(Latitude);
1681 0 : CPLFree(Longitude);
1682 : }
1683 :
1684 0 : if (LatitudeDatasetID > 0)
1685 0 : H5Dclose(LatitudeDatasetID);
1686 0 : if (LongitudeDatasetID > 0)
1687 0 : H5Dclose(LongitudeDatasetID);
1688 :
1689 0 : break;
1690 : }
1691 : }
1692 :
1693 2 : return CE_None;
1694 : }
1695 :
1696 : /************************************************************************/
1697 : /* GetMetadataItem() */
1698 : /************************************************************************/
1699 :
1700 123 : const char *HDF5ImageDataset::GetMetadataItem(const char *pszName,
1701 : const char *pszDomain)
1702 : {
1703 123 : if (pszDomain && EQUAL(pszDomain, "__DEBUG__") &&
1704 66 : EQUAL(pszName, "WholeBandChunkOptim"))
1705 : {
1706 66 : switch (m_eWholeBandChunkOptim)
1707 : {
1708 4 : case WBC_DETECTION_IN_PROGRESS:
1709 4 : return "DETECTION_IN_PROGRESS";
1710 3 : case WBC_DISABLED:
1711 3 : return "DISABLED";
1712 59 : case WBC_ENABLED:
1713 59 : return "ENABLED";
1714 : }
1715 : }
1716 57 : return GDALPamDataset::GetMetadataItem(pszName, pszDomain);
1717 : }
1718 :
1719 : /************************************************************************/
1720 : /* GetSpatialRef() */
1721 : /************************************************************************/
1722 :
1723 12 : const OGRSpatialReference *HDF5ImageDataset::GetSpatialRef() const
1724 : {
1725 12 : if (!m_oSRS.IsEmpty())
1726 11 : return &m_oSRS;
1727 1 : return GDALPamDataset::GetSpatialRef();
1728 : }
1729 :
1730 : /************************************************************************/
1731 : /* GetGCPCount() */
1732 : /************************************************************************/
1733 :
1734 3 : int HDF5ImageDataset::GetGCPCount()
1735 :
1736 : {
1737 3 : if (!m_aoGCPs.empty())
1738 1 : return static_cast<int>(m_aoGCPs.size());
1739 :
1740 2 : return GDALPamDataset::GetGCPCount();
1741 : }
1742 :
1743 : /************************************************************************/
1744 : /* GetGCPSpatialRef() */
1745 : /************************************************************************/
1746 :
1747 1 : const OGRSpatialReference *HDF5ImageDataset::GetGCPSpatialRef() const
1748 :
1749 : {
1750 1 : if (!m_aoGCPs.empty() && !m_oGCPSRS.IsEmpty())
1751 1 : return &m_oGCPSRS;
1752 :
1753 0 : return GDALPamDataset::GetGCPSpatialRef();
1754 : }
1755 :
1756 : /************************************************************************/
1757 : /* GetGCPs() */
1758 : /************************************************************************/
1759 :
1760 2 : const GDAL_GCP *HDF5ImageDataset::GetGCPs()
1761 : {
1762 2 : if (!m_aoGCPs.empty())
1763 1 : return gdal::GCP::c_ptr(m_aoGCPs);
1764 :
1765 1 : return GDALPamDataset::GetGCPs();
1766 : }
1767 :
1768 : /************************************************************************/
1769 : /* GetGeoTransform() */
1770 : /************************************************************************/
1771 :
1772 8 : CPLErr HDF5ImageDataset::GetGeoTransform(GDALGeoTransform >) const
1773 : {
1774 8 : if (bHasGeoTransform)
1775 : {
1776 7 : gt = m_gt;
1777 7 : return CE_None;
1778 : }
1779 :
1780 1 : return GDALPamDataset::GetGeoTransform(gt);
1781 : }
1782 :
1783 : /************************************************************************/
1784 : /* IdentifyProductType() */
1785 : /************************************************************************/
1786 :
1787 : /**
1788 : * Identify if the subdataset has a known product format
1789 : * It stores a product identifier in iSubdatasetType,
1790 : * UNKNOWN_PRODUCT, if it isn't a recognizable format.
1791 : */
1792 59 : void HDF5ImageDataset::IdentifyProductType()
1793 : {
1794 59 : iSubdatasetType = UNKNOWN_PRODUCT;
1795 :
1796 : // COSMO-SKYMED
1797 :
1798 : // Get the Mission Id as a char *, because the field may not exist.
1799 59 : const char *const pszMissionId = HDF5Dataset::GetMetadataItem("Mission_ID");
1800 :
1801 : // If there is a Mission_ID field.
1802 59 : if (pszMissionId != nullptr && strstr(GetDescription(), "QLK") == nullptr)
1803 : {
1804 : // Check if the mission type is CSK, KMPS or CSG.
1805 : // KMPS: Komsat-5 is Korean mission with a SAR instrument.
1806 : // CSG: Cosmo Skymed 2nd Generation
1807 2 : if (EQUAL(pszMissionId, "CSK") || EQUAL(pszMissionId, "KMPS") ||
1808 0 : EQUAL(pszMissionId, "CSG"))
1809 : {
1810 2 : iSubdatasetType = CSK_PRODUCT;
1811 :
1812 2 : if (GetMetadataItem("Product_Type") != nullptr)
1813 : {
1814 : // Get the format's level.
1815 : const char *osMissionLevel =
1816 2 : HDF5Dataset::GetMetadataItem("Product_Type");
1817 :
1818 2 : if (STARTS_WITH_CI(osMissionLevel, "RAW"))
1819 0 : iCSKProductType = PROD_CSK_L0;
1820 :
1821 2 : if (STARTS_WITH_CI(osMissionLevel, "SCS"))
1822 0 : iCSKProductType = PROD_CSK_L1A;
1823 :
1824 2 : if (STARTS_WITH_CI(osMissionLevel, "DGM"))
1825 1 : iCSKProductType = PROD_CSK_L1B;
1826 :
1827 2 : if (STARTS_WITH_CI(osMissionLevel, "GEC"))
1828 1 : iCSKProductType = PROD_CSK_L1C;
1829 :
1830 2 : if (STARTS_WITH_CI(osMissionLevel, "GTC"))
1831 0 : iCSKProductType = PROD_CSK_L1D;
1832 : }
1833 : }
1834 : }
1835 59 : }
1836 :
1837 : /************************************************************************/
1838 : /* CaptureCSKGeolocation() */
1839 : /************************************************************************/
1840 :
1841 : /**
1842 : * Captures Geolocation information from a COSMO-SKYMED
1843 : * file.
1844 : * The geoid will always be WGS84
1845 : * The projection type may be UTM or UPS, depending on the
1846 : * latitude from the center of the image.
1847 : * @param iProductType type of CSK subproduct, see HDF5CSKProduct
1848 : */
1849 2 : void HDF5ImageDataset::CaptureCSKGeolocation(int iProductType)
1850 : {
1851 : // Set the ellipsoid to WGS84.
1852 2 : m_oSRS.SetWellKnownGeogCS("WGS84");
1853 :
1854 2 : if (iProductType == PROD_CSK_L1C || iProductType == PROD_CSK_L1D)
1855 : {
1856 1 : double *dfProjFalseEastNorth = nullptr;
1857 1 : double *dfProjScaleFactor = nullptr;
1858 1 : double *dfCenterCoord = nullptr;
1859 :
1860 : // Check if all the metadata attributes are present.
1861 1 : if (HDF5ReadDoubleAttr("Map Projection False East-North",
1862 1 : &dfProjFalseEastNorth) == CE_Failure ||
1863 1 : HDF5ReadDoubleAttr("Map Projection Scale Factor",
1864 1 : &dfProjScaleFactor) == CE_Failure ||
1865 1 : HDF5ReadDoubleAttr("Map Projection Centre", &dfCenterCoord) ==
1866 2 : CE_Failure ||
1867 1 : GetMetadataItem("Projection_ID") == nullptr)
1868 : {
1869 0 : m_oSRS.Clear();
1870 0 : m_oGCPSRS.Clear();
1871 0 : CPLError(CE_Failure, CPLE_OpenFailed,
1872 : "The CSK hdf5 file geolocation information is "
1873 : "malformed");
1874 : }
1875 : else
1876 : {
1877 : // Fetch projection Type.
1878 2 : CPLString osProjectionID = GetMetadataItem("Projection_ID");
1879 :
1880 : // If the projection is UTM.
1881 1 : if (EQUAL(osProjectionID, "UTM"))
1882 : {
1883 : // @TODO: use SetUTM
1884 1 : m_oSRS.SetProjCS(SRS_PT_TRANSVERSE_MERCATOR);
1885 1 : m_oSRS.SetTM(dfCenterCoord[0], dfCenterCoord[1],
1886 : dfProjScaleFactor[0], dfProjFalseEastNorth[0],
1887 1 : dfProjFalseEastNorth[1]);
1888 : }
1889 : else
1890 : {
1891 : // TODO: No UPS projected files to test!
1892 : // If the projection is UPS.
1893 0 : if (EQUAL(osProjectionID, "UPS"))
1894 : {
1895 0 : m_oSRS.SetProjCS(SRS_PT_POLAR_STEREOGRAPHIC);
1896 0 : m_oSRS.SetPS(dfCenterCoord[0], dfCenterCoord[1],
1897 : dfProjScaleFactor[0], dfProjFalseEastNorth[0],
1898 0 : dfProjFalseEastNorth[1]);
1899 : }
1900 : }
1901 :
1902 1 : CPLFree(dfCenterCoord);
1903 1 : CPLFree(dfProjScaleFactor);
1904 1 : CPLFree(dfProjFalseEastNorth);
1905 1 : }
1906 : }
1907 : else
1908 : {
1909 1 : m_oGCPSRS = m_oSRS;
1910 : }
1911 2 : }
1912 :
1913 : /************************************************************************/
1914 : /* CaptureCSKGeoTransform() */
1915 : /************************************************************************/
1916 :
1917 : /**
1918 : * Get Geotransform information for COSMO-SKYMED files
1919 : * In case of success it stores the transformation
1920 : * in m_gt. In case of failure it doesn't
1921 : * modify m_gt
1922 : * @param iProductType type of CSK subproduct, see HDF5CSKProduct
1923 : */
1924 2 : void HDF5ImageDataset::CaptureCSKGeoTransform(int iProductType)
1925 : {
1926 2 : const char *pszSubdatasetName = GetSubdatasetName();
1927 :
1928 2 : bHasGeoTransform = false;
1929 : // If the product level is not L1C or L1D then
1930 : // it doesn't have a valid projection.
1931 2 : if (iProductType == PROD_CSK_L1C || iProductType == PROD_CSK_L1D)
1932 : {
1933 : // If there is a subdataset.
1934 1 : if (pszSubdatasetName != nullptr)
1935 : {
1936 2 : CPLString osULPath = pszSubdatasetName;
1937 1 : osULPath += "/Top Left East-North";
1938 :
1939 2 : CPLString osLineSpacingPath = pszSubdatasetName;
1940 1 : osLineSpacingPath += "/Line Spacing";
1941 :
1942 2 : CPLString osColumnSpacingPath = pszSubdatasetName;
1943 1 : osColumnSpacingPath += "/Column Spacing";
1944 :
1945 1 : double *pdOutUL = nullptr;
1946 1 : double *pdLineSpacing = nullptr;
1947 1 : double *pdColumnSpacing = nullptr;
1948 :
1949 : // If it could find the attributes on the metadata.
1950 1 : if (HDF5ReadDoubleAttr(osULPath.c_str(), &pdOutUL) == CE_Failure ||
1951 1 : HDF5ReadDoubleAttr(osLineSpacingPath.c_str(), &pdLineSpacing) ==
1952 2 : CE_Failure ||
1953 1 : HDF5ReadDoubleAttr(osColumnSpacingPath.c_str(),
1954 : &pdColumnSpacing) == CE_Failure)
1955 : {
1956 0 : bHasGeoTransform = false;
1957 : }
1958 : else
1959 : {
1960 : // geotransform[1] : width of pixel
1961 : // geotransform[4] : rotational coefficient, zero for north up
1962 : // images.
1963 : // geotransform[2] : rotational coefficient, zero for north up
1964 : // images.
1965 : // geotransform[5] : height of pixel (but negative)
1966 :
1967 1 : m_gt[0] = pdOutUL[0];
1968 1 : m_gt[1] = pdLineSpacing[0];
1969 1 : m_gt[2] = 0;
1970 1 : m_gt[3] = pdOutUL[1];
1971 1 : m_gt[4] = 0;
1972 1 : m_gt[5] = -pdColumnSpacing[0];
1973 :
1974 1 : CPLFree(pdOutUL);
1975 1 : CPLFree(pdLineSpacing);
1976 1 : CPLFree(pdColumnSpacing);
1977 :
1978 1 : bHasGeoTransform = true;
1979 : }
1980 : }
1981 : }
1982 2 : }
1983 :
1984 : /************************************************************************/
1985 : /* CaptureCSKGCPs() */
1986 : /************************************************************************/
1987 :
1988 : /**
1989 : * Retrieves and stores the GCPs from a COSMO-SKYMED dataset
1990 : * It only retrieves the GCPs for L0, L1A and L1B products
1991 : * for L1C and L1D products, geotransform is provided.
1992 : * The GCPs provided will be the Image's corners.
1993 : * @param iProductType type of CSK product @see HDF5CSKProductEnum
1994 : */
1995 2 : void HDF5ImageDataset::CaptureCSKGCPs(int iProductType)
1996 : {
1997 : // Only retrieve GCPs for L0,L1A and L1B products.
1998 2 : if (iProductType == PROD_CSK_L0 || iProductType == PROD_CSK_L1A ||
1999 : iProductType == PROD_CSK_L1B)
2000 : {
2001 10 : CPLString osCornerName[4];
2002 1 : double pdCornerPixel[4] = {0.0, 0.0, 0.0, 0.0};
2003 1 : double pdCornerLine[4] = {0.0, 0.0, 0.0, 0.0};
2004 :
2005 1 : const char *const pszSubdatasetName = GetSubdatasetName();
2006 :
2007 : // Load the subdataset name first.
2008 5 : for (int i = 0; i < 4; i++)
2009 4 : osCornerName[i] = pszSubdatasetName;
2010 :
2011 : // Load the attribute name, and raster coordinates for
2012 : // all the corners.
2013 1 : osCornerName[0] += "/Top Left Geodetic Coordinates";
2014 1 : pdCornerPixel[0] = 0;
2015 1 : pdCornerLine[0] = 0;
2016 :
2017 1 : osCornerName[1] += "/Top Right Geodetic Coordinates";
2018 1 : pdCornerPixel[1] = GetRasterXSize();
2019 1 : pdCornerLine[1] = 0;
2020 :
2021 1 : osCornerName[2] += "/Bottom Left Geodetic Coordinates";
2022 1 : pdCornerPixel[2] = 0;
2023 1 : pdCornerLine[2] = GetRasterYSize();
2024 :
2025 1 : osCornerName[3] += "/Bottom Right Geodetic Coordinates";
2026 1 : pdCornerPixel[3] = GetRasterXSize();
2027 1 : pdCornerLine[3] = GetRasterYSize();
2028 :
2029 : // For all the image's corners.
2030 5 : for (int i = 0; i < 4; i++)
2031 : {
2032 4 : double *pdCornerCoordinates = nullptr;
2033 :
2034 : // Retrieve the attributes.
2035 4 : if (HDF5ReadDoubleAttr(osCornerName[i].c_str(),
2036 4 : &pdCornerCoordinates) == CE_Failure)
2037 : {
2038 0 : CPLError(CE_Failure, CPLE_OpenFailed,
2039 : "Error retrieving CSK GCPs");
2040 0 : m_aoGCPs.clear();
2041 0 : break;
2042 : }
2043 :
2044 0 : m_aoGCPs.emplace_back(osCornerName[i].c_str(), "", pdCornerPixel[i],
2045 4 : pdCornerLine[i],
2046 4 : /* X = */ pdCornerCoordinates[1],
2047 : /* Y = */ pdCornerCoordinates[0],
2048 4 : /* Z = */ pdCornerCoordinates[2]);
2049 :
2050 : // Free the returned coordinates.
2051 4 : CPLFree(pdCornerCoordinates);
2052 : }
2053 : }
2054 2 : }
|