Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: Hierarchical Data Format Release 5 (HDF5)
4 : * Purpose: Read S104 datasets.
5 : * Author: Even Rouault <even dot rouault at spatialys dot com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2023, Even Rouault <even dot rouault at spatialys dot com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_port.h"
14 : #include "hdf5dataset.h"
15 : #include "hdf5drivercore.h"
16 : #include "gh5_convenience.h"
17 : #include "rat.h"
18 : #include "s100.h"
19 :
20 : #include "gdal_priv.h"
21 : #include "gdal_proxy.h"
22 : #include "gdal_rat.h"
23 :
24 : #include <limits>
25 :
26 : /************************************************************************/
27 : /* S104Dataset */
28 : /************************************************************************/
29 :
30 : class S104Dataset final : public S100BaseDataset
31 : {
32 : public:
33 4 : explicit S104Dataset(const std::string &osFilename)
34 4 : : S100BaseDataset(osFilename)
35 : {
36 4 : }
37 :
38 : static GDALDataset *Open(GDALOpenInfo *);
39 : };
40 :
41 : /************************************************************************/
42 : /* S104RasterBand */
43 : /************************************************************************/
44 :
45 : class S104RasterBand final : public GDALProxyRasterBand
46 : {
47 : friend class S104Dataset;
48 : std::unique_ptr<GDALDataset> m_poDS{};
49 : GDALRasterBand *m_poUnderlyingBand = nullptr;
50 : std::string m_osUnitType{};
51 : std::unique_ptr<GDALRasterAttributeTable> m_poRAT{};
52 :
53 : public:
54 4 : explicit S104RasterBand(std::unique_ptr<GDALDataset> &&poDSIn)
55 8 : : m_poDS(std::move(poDSIn)),
56 4 : m_poUnderlyingBand(m_poDS->GetRasterBand(1))
57 : {
58 4 : eDataType = m_poUnderlyingBand->GetRasterDataType();
59 4 : m_poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
60 4 : }
61 :
62 : GDALRasterBand *
63 8 : RefUnderlyingRasterBand(bool /*bForceOpen*/ = true) const override
64 : {
65 8 : return m_poUnderlyingBand;
66 : }
67 :
68 2 : const char *GetUnitType() override
69 : {
70 2 : return m_osUnitType.c_str();
71 : }
72 :
73 1 : GDALRasterAttributeTable *GetDefaultRAT() override
74 : {
75 1 : return m_poRAT.get();
76 : }
77 : };
78 :
79 : /************************************************************************/
80 : /* Open() */
81 : /************************************************************************/
82 :
83 5 : GDALDataset *S104Dataset::Open(GDALOpenInfo *poOpenInfo)
84 :
85 : {
86 : // Confirm that this appears to be a S104 file.
87 5 : if (!S104DatasetIdentify(poOpenInfo))
88 0 : return nullptr;
89 :
90 : HDF5_GLOBAL_LOCK();
91 :
92 5 : if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
93 : {
94 1 : return HDF5Dataset::OpenMultiDim(poOpenInfo);
95 : }
96 :
97 : // Confirm the requested access is supported.
98 4 : if (poOpenInfo->eAccess == GA_Update)
99 : {
100 0 : CPLError(CE_Failure, CPLE_NotSupported,
101 : "The S104 driver does not support update access.");
102 0 : return nullptr;
103 : }
104 :
105 8 : std::string osFilename(poOpenInfo->pszFilename);
106 8 : std::string osGroup;
107 4 : if (STARTS_WITH(poOpenInfo->pszFilename, "S104:"))
108 : {
109 : const CPLStringList aosTokens(
110 3 : CSLTokenizeString2(poOpenInfo->pszFilename, ":",
111 3 : CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES));
112 :
113 3 : if (aosTokens.size() == 2)
114 : {
115 0 : osFilename = aosTokens[1];
116 : }
117 3 : else if (aosTokens.size() == 3)
118 : {
119 3 : osFilename = aosTokens[1];
120 3 : osGroup = aosTokens[2];
121 : }
122 : else
123 : {
124 0 : return nullptr;
125 : }
126 : }
127 :
128 8 : auto poDS = std::make_unique<S104Dataset>(osFilename);
129 4 : if (!poDS->Init())
130 0 : return nullptr;
131 :
132 4 : const auto &poRootGroup = poDS->m_poRootGroup;
133 :
134 12 : auto poWaterLevel = poRootGroup->OpenGroup("WaterLevel");
135 4 : if (!poWaterLevel)
136 : {
137 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find /WaterLevel group");
138 0 : return nullptr;
139 : }
140 :
141 12 : auto poDataCodingFormat = poWaterLevel->GetAttribute("dataCodingFormat");
142 8 : if (!poDataCodingFormat ||
143 4 : poDataCodingFormat->GetDataType().GetClass() != GEDTC_NUMERIC)
144 : {
145 0 : CPLError(CE_Failure, CPLE_AppDefined,
146 : "Cannot find /WaterLevel/dataCodingFormat attribute");
147 0 : return nullptr;
148 : }
149 4 : const int nDataCodingFormat = poDataCodingFormat->ReadAsInt();
150 4 : if (nDataCodingFormat != 2)
151 : {
152 0 : CPLError(CE_Failure, CPLE_NotSupported,
153 : "dataCodingFormat=%d is not supported by the S104 driver",
154 : nDataCodingFormat);
155 0 : return nullptr;
156 : }
157 :
158 : // Read additional metadata
159 12 : for (const char *pszAttrName :
160 16 : {"methodWaterLevelProduct", "minDatasetHeight", "maxDatasetHeight"})
161 : {
162 36 : auto poAttr = poWaterLevel->GetAttribute(pszAttrName);
163 12 : if (poAttr)
164 : {
165 8 : const char *pszVal = poAttr->ReadAsString();
166 8 : if (pszVal)
167 : {
168 8 : poDS->GDALDataset::SetMetadataItem(pszAttrName, pszVal);
169 : }
170 : }
171 : }
172 :
173 12 : auto poWaterLevel01 = poWaterLevel->OpenGroup("WaterLevel.01");
174 4 : if (!poWaterLevel01)
175 : {
176 0 : CPLError(CE_Failure, CPLE_AppDefined,
177 : "Cannot find /WaterLevel/WaterLevel.01 group");
178 0 : return nullptr;
179 : }
180 :
181 : // Read additional metadata
182 16 : for (const char *pszAttrName :
183 : {"timeRecordInterval", "dateTimeOfFirstRecord", "dateTimeOfLastRecord",
184 20 : "numberOfTimes"})
185 : {
186 48 : auto poAttr = poWaterLevel01->GetAttribute(pszAttrName);
187 16 : if (poAttr)
188 : {
189 16 : const char *pszVal = poAttr->ReadAsString();
190 16 : if (pszVal)
191 : {
192 16 : poDS->GDALDataset::SetMetadataItem(pszAttrName, pszVal);
193 : }
194 : }
195 : }
196 :
197 8 : if (auto poStartSequence = poWaterLevel01->GetAttribute("startSequence"))
198 : {
199 4 : const char *pszStartSequence = poStartSequence->ReadAsString();
200 4 : if (pszStartSequence && !EQUAL(pszStartSequence, "0,0"))
201 : {
202 0 : CPLError(CE_Failure, CPLE_AppDefined,
203 : "startSequence (=%s) != 0,0 is not supported",
204 : pszStartSequence);
205 0 : return nullptr;
206 : }
207 : }
208 :
209 4 : if (!S100GetNumPointsLongitudinalLatitudinal(
210 4 : poWaterLevel01.get(), poDS->nRasterXSize, poDS->nRasterYSize))
211 : {
212 0 : return nullptr;
213 : }
214 :
215 4 : const bool bNorthUp = CPLTestBool(
216 4 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "NORTH_UP", "YES"));
217 :
218 : // Compute geotransform
219 4 : poDS->m_bHasGT = S100GetGeoTransform(poWaterLevel01.get(),
220 4 : poDS->m_adfGeoTransform, bNorthUp);
221 :
222 4 : if (osGroup.empty())
223 : {
224 2 : const auto aosGroupNames = poWaterLevel01->GetGroupNames();
225 1 : int iSubDS = 1;
226 2 : for (const auto &osSubGroup : aosGroupNames)
227 : {
228 2 : if (auto poSubGroup = poWaterLevel01->OpenGroup(osSubGroup))
229 : {
230 1 : poDS->GDALDataset::SetMetadataItem(
231 : CPLSPrintf("SUBDATASET_%d_NAME", iSubDS),
232 : CPLSPrintf("S104:\"%s\":%s", osFilename.c_str(),
233 : osSubGroup.c_str()),
234 : "SUBDATASETS");
235 2 : std::string osSubDSDesc = "Values for group ";
236 1 : osSubDSDesc += osSubGroup;
237 2 : const auto poTimePoint = poSubGroup->GetAttribute("timePoint");
238 1 : if (poTimePoint)
239 : {
240 1 : const char *pszVal = poTimePoint->ReadAsString();
241 1 : if (pszVal)
242 : {
243 1 : osSubDSDesc = "Values at timestamp ";
244 1 : osSubDSDesc += pszVal;
245 : }
246 : }
247 1 : poDS->GDALDataset::SetMetadataItem(
248 : CPLSPrintf("SUBDATASET_%d_DESC", iSubDS),
249 : osSubDSDesc.c_str(), "SUBDATASETS");
250 1 : ++iSubDS;
251 : }
252 : }
253 : }
254 : else
255 : {
256 3 : auto poGroup = poWaterLevel01->OpenGroup(osGroup);
257 3 : if (!poGroup)
258 : {
259 1 : CPLError(CE_Failure, CPLE_AppDefined,
260 : "Cannot find /WaterLevel/WaterLevel.01/%s group",
261 : osGroup.c_str());
262 1 : return nullptr;
263 : }
264 :
265 4 : auto poValuesArray = poGroup->OpenMDArray("values");
266 2 : if (!poValuesArray)
267 : {
268 0 : CPLError(CE_Failure, CPLE_AppDefined,
269 : "Cannot find /WaterLevel/WaterLevel.01/%s/values array",
270 : osGroup.c_str());
271 0 : return nullptr;
272 : }
273 :
274 2 : if (poValuesArray->GetDimensionCount() != 2)
275 : {
276 0 : CPLError(CE_Failure, CPLE_AppDefined,
277 : "Wrong dimension count for %s",
278 0 : poValuesArray->GetFullName().c_str());
279 0 : return nullptr;
280 : }
281 :
282 2 : const auto &oType = poValuesArray->GetDataType();
283 2 : if (oType.GetClass() != GEDTC_COMPOUND)
284 : {
285 0 : CPLError(CE_Failure, CPLE_AppDefined, "Wrong data type for %s",
286 0 : poValuesArray->GetFullName().c_str());
287 0 : return nullptr;
288 : }
289 :
290 2 : const auto &oComponents = oType.GetComponents();
291 2 : if (oComponents.size() != 2 ||
292 4 : oComponents[0]->GetName() != "waterLevelHeight" ||
293 2 : oComponents[0]->GetType().GetNumericDataType() != GDT_Float32 ||
294 6 : oComponents[1]->GetName() != "waterLevelTrend" ||
295 2 : (oComponents[1]->GetType().GetNumericDataType() != GDT_Byte &&
296 : // In theory should be Byte, but 104US00_ches_dcf2_20190606T12Z.h5 uses Int32
297 0 : oComponents[1]->GetType().GetNumericDataType() != GDT_Int32))
298 : {
299 0 : CPLError(CE_Failure, CPLE_AppDefined, "Wrong data type for %s",
300 0 : poValuesArray->GetFullName().c_str());
301 0 : return nullptr;
302 : }
303 :
304 2 : const auto &apoDims = poValuesArray->GetDimensions();
305 2 : if (apoDims[0]->GetSize() != static_cast<unsigned>(poDS->nRasterYSize))
306 : {
307 0 : CPLError(CE_Failure, CPLE_AppDefined,
308 : "numPointsLatitudinal(=%d) doesn't match first dimension "
309 : "size of %s (=%d)",
310 0 : poDS->nRasterYSize, poValuesArray->GetFullName().c_str(),
311 0 : static_cast<int>(apoDims[0]->GetSize()));
312 0 : return nullptr;
313 : }
314 2 : if (apoDims[1]->GetSize() != static_cast<unsigned>(poDS->nRasterXSize))
315 : {
316 0 : CPLError(CE_Failure, CPLE_AppDefined,
317 : "numPointsLongitudinal(=%d) doesn't match second "
318 : "dimension size of %s (=%d)",
319 0 : poDS->nRasterXSize, poValuesArray->GetFullName().c_str(),
320 0 : static_cast<int>(apoDims[1]->GetSize()));
321 0 : return nullptr;
322 : }
323 :
324 2 : if (bNorthUp)
325 1 : poValuesArray = poValuesArray->GetView("[::-1,...]");
326 :
327 : // Create waterLevelHeight band
328 : auto poWaterLevelHeight =
329 6 : poValuesArray->GetView("[\"waterLevelHeight\"]");
330 : auto poWaterLevelHeightDS = std::unique_ptr<GDALDataset>(
331 4 : poWaterLevelHeight->AsClassicDataset(1, 0));
332 : auto poWaterLevelHeightBand =
333 4 : std::make_unique<S104RasterBand>(std::move(poWaterLevelHeightDS));
334 2 : poWaterLevelHeightBand->SetDescription("waterLevelHeight");
335 2 : poWaterLevelHeightBand->m_osUnitType = "metre";
336 2 : poDS->SetBand(1, poWaterLevelHeightBand.release());
337 :
338 : // Create waterLevelTrend band
339 : auto poWaterLevelTrend =
340 6 : poValuesArray->GetView("[\"waterLevelTrend\"]");
341 : auto poWaterLevelTrendDS = std::unique_ptr<GDALDataset>(
342 4 : poWaterLevelTrend->AsClassicDataset(1, 0));
343 : auto poWaterLevelTrendBand =
344 4 : std::make_unique<S104RasterBand>(std::move(poWaterLevelTrendDS));
345 2 : poWaterLevelTrendBand->SetDescription("waterLevelTrend");
346 :
347 : // From D-5.3 Water Level Trend of S-101 v1.1 spec
348 4 : auto poRAT = std::make_unique<GDALDefaultRasterAttributeTable>();
349 2 : poRAT->CreateColumn("code", GFT_Integer, GFU_MinMax);
350 2 : poRAT->CreateColumn("label", GFT_String, GFU_Generic);
351 2 : poRAT->CreateColumn("definition", GFT_String, GFU_Generic);
352 :
353 : const struct
354 : {
355 : int nCode;
356 : const char *pszLabel;
357 : const char *pszDefinition;
358 2 : } aoRatValues[] = {
359 : {0, "Nodata", "No data"},
360 : {1, "Decreasing", "Becoming smaller in magnitude"},
361 : {2, "Increasing", "Becoming larger in magnitude"},
362 : {3, "Steady", "Constant"},
363 : };
364 :
365 2 : int iRow = 0;
366 10 : for (const auto &oRecord : aoRatValues)
367 : {
368 8 : int iCol = 0;
369 8 : poRAT->SetValue(iRow, iCol++, oRecord.nCode);
370 8 : poRAT->SetValue(iRow, iCol++, oRecord.pszLabel);
371 8 : poRAT->SetValue(iRow, iCol++, oRecord.pszDefinition);
372 8 : ++iRow;
373 : }
374 :
375 2 : poWaterLevelTrendBand->m_poRAT = std::move(poRAT);
376 :
377 2 : poDS->SetBand(2, poWaterLevelTrendBand.release());
378 : }
379 :
380 3 : poDS->GDALDataset::SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT);
381 :
382 : // Setup/check for pam .aux.xml.
383 3 : poDS->SetDescription(osFilename.c_str());
384 3 : poDS->TryLoadXML();
385 :
386 : // Setup overviews.
387 3 : poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
388 :
389 3 : return poDS.release();
390 : }
391 :
392 : /************************************************************************/
393 : /* S104DatasetDriverUnload() */
394 : /************************************************************************/
395 :
396 6 : static void S104DatasetDriverUnload(GDALDriver *)
397 : {
398 6 : HDF5UnloadFileDriver();
399 6 : }
400 :
401 : /************************************************************************/
402 : /* GDALRegister_S104() */
403 : /************************************************************************/
404 10 : void GDALRegister_S104()
405 :
406 : {
407 10 : if (!GDAL_CHECK_VERSION("S104"))
408 0 : return;
409 :
410 10 : if (GDALGetDriverByName(S104_DRIVER_NAME) != nullptr)
411 0 : return;
412 :
413 10 : GDALDriver *poDriver = new GDALDriver();
414 :
415 10 : S104DriverSetCommonMetadata(poDriver);
416 10 : poDriver->pfnOpen = S104Dataset::Open;
417 10 : poDriver->pfnUnloadDriver = S104DatasetDriverUnload;
418 :
419 10 : GetGDALDriverManager()->RegisterDriver(poDriver);
420 : }
|