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_frmts.h"
21 : #include "gdal_priv.h"
22 : #include "gdal_proxy.h"
23 : #include "gdal_rat.h"
24 :
25 : #include <limits>
26 : #include <map>
27 :
28 : /************************************************************************/
29 : /* S104Dataset */
30 : /************************************************************************/
31 :
32 42 : class S104Dataset final : public S100BaseDataset
33 : {
34 : public:
35 21 : explicit S104Dataset(const std::string &osFilename)
36 21 : : S100BaseDataset(osFilename)
37 : {
38 21 : }
39 :
40 : ~S104Dataset() override;
41 :
42 : static GDALDataset *Open(GDALOpenInfo *);
43 : };
44 :
45 : S104Dataset::~S104Dataset() = default;
46 :
47 : /************************************************************************/
48 : /* S104RasterBand */
49 : /************************************************************************/
50 :
51 : class S104RasterBand final : public GDALProxyRasterBand
52 : {
53 : friend class S104Dataset;
54 : std::unique_ptr<GDALDataset> m_poDS{};
55 : GDALRasterBand *m_poUnderlyingBand = nullptr;
56 : std::string m_osUnitType{};
57 : std::unique_ptr<GDALRasterAttributeTable> m_poRAT{};
58 :
59 : CPL_DISALLOW_COPY_ASSIGN(S104RasterBand)
60 :
61 : public:
62 8 : explicit S104RasterBand(std::unique_ptr<GDALDataset> &&poDSIn)
63 16 : : m_poDS(std::move(poDSIn)),
64 8 : m_poUnderlyingBand(m_poDS->GetRasterBand(1))
65 : {
66 8 : eDataType = m_poUnderlyingBand->GetRasterDataType();
67 8 : m_poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
68 8 : }
69 :
70 : GDALRasterBand *
71 : RefUnderlyingRasterBand(bool /*bForceOpen*/ = true) const override;
72 :
73 2 : const char *GetUnitType() override
74 : {
75 2 : return m_osUnitType.c_str();
76 : }
77 :
78 1 : GDALRasterAttributeTable *GetDefaultRAT() override
79 : {
80 1 : return m_poRAT.get();
81 : }
82 : };
83 :
84 : GDALRasterBand *
85 12 : S104RasterBand::RefUnderlyingRasterBand(bool /*bForceOpen*/) const
86 : {
87 12 : return m_poUnderlyingBand;
88 : }
89 :
90 : /************************************************************************/
91 : /* Open() */
92 : /************************************************************************/
93 :
94 22 : GDALDataset *S104Dataset::Open(GDALOpenInfo *poOpenInfo)
95 :
96 : {
97 : // Confirm that this appears to be a S104 file.
98 22 : if (!S104DatasetIdentify(poOpenInfo))
99 0 : return nullptr;
100 :
101 : HDF5_GLOBAL_LOCK();
102 :
103 22 : if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
104 : {
105 1 : return HDF5Dataset::OpenMultiDim(poOpenInfo);
106 : }
107 :
108 : // Confirm the requested access is supported.
109 21 : if (poOpenInfo->eAccess == GA_Update)
110 : {
111 0 : ReportUpdateNotSupportedByDriver("S104");
112 0 : return nullptr;
113 : }
114 :
115 42 : std::string osFilename(poOpenInfo->pszFilename);
116 42 : std::string osFeatureInstance = "WaterLevel.01";
117 42 : std::string osGroup;
118 21 : if (STARTS_WITH(poOpenInfo->pszFilename, "S104:"))
119 : {
120 : const CPLStringList aosTokens(
121 5 : CSLTokenizeString2(poOpenInfo->pszFilename, ":",
122 5 : CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES));
123 :
124 5 : if (aosTokens.size() == 2)
125 : {
126 0 : osFilename = aosTokens[1];
127 : }
128 5 : else if (aosTokens.size() == 3)
129 : {
130 3 : osFilename = aosTokens[1];
131 3 : osGroup = aosTokens[2];
132 : }
133 2 : else if (aosTokens.size() == 4)
134 : {
135 2 : osFilename = aosTokens[1];
136 2 : osFeatureInstance = aosTokens[2];
137 2 : osGroup = aosTokens[3];
138 : }
139 : else
140 : {
141 0 : return nullptr;
142 : }
143 : }
144 :
145 42 : auto poDS = std::make_unique<S104Dataset>(osFilename);
146 21 : if (!poDS->Init())
147 0 : return nullptr;
148 :
149 21 : const auto &poRootGroup = poDS->m_poRootGroup;
150 :
151 63 : auto poVerticalCS = poRootGroup->GetAttribute("verticalCS");
152 21 : if (poVerticalCS && poVerticalCS->GetDataType().GetClass() == GEDTC_NUMERIC)
153 : {
154 21 : const int nVerticalCS = poVerticalCS->ReadAsInt();
155 21 : if (nVerticalCS == 6498)
156 21 : poDS->GDALDataset::SetMetadataItem(
157 : "VERTICAL_CS_MEANING", "depth, meters, orientation down");
158 0 : else if (nVerticalCS == 6499)
159 0 : poDS->GDALDataset::SetMetadataItem(
160 : "VERTICAL_CS_MEANING", "height, meters, orientation up");
161 :
162 42 : poDS->GDALDataset::SetMetadataItem("verticalCS",
163 42 : std::to_string(nVerticalCS).c_str());
164 : }
165 :
166 63 : auto poWaterLevel = poRootGroup->OpenGroup("WaterLevel");
167 21 : if (!poWaterLevel)
168 : {
169 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find /WaterLevel group");
170 0 : return nullptr;
171 : }
172 :
173 63 : auto poDataCodingFormat = poWaterLevel->GetAttribute("dataCodingFormat");
174 42 : if (!poDataCodingFormat ||
175 21 : poDataCodingFormat->GetDataType().GetClass() != GEDTC_NUMERIC)
176 : {
177 0 : CPLError(CE_Failure, CPLE_AppDefined,
178 : "Cannot find /WaterLevel/dataCodingFormat attribute");
179 0 : return nullptr;
180 : }
181 21 : const int nDataCodingFormat = poDataCodingFormat->ReadAsInt();
182 21 : if (nDataCodingFormat != 2)
183 : {
184 0 : CPLError(CE_Failure, CPLE_NotSupported,
185 : "dataCodingFormat=%d is not supported by the S104 driver",
186 : nDataCodingFormat);
187 0 : return nullptr;
188 : }
189 :
190 : // Read additional metadata
191 63 : for (const char *pszAttrName :
192 84 : {"methodWaterLevelProduct", "minDatasetHeight", "maxDatasetHeight"})
193 : {
194 189 : auto poAttr = poWaterLevel->GetAttribute(pszAttrName);
195 63 : if (poAttr)
196 : {
197 42 : const char *pszVal = poAttr->ReadAsString();
198 42 : if (pszVal)
199 : {
200 42 : poDS->GDALDataset::SetMetadataItem(pszAttrName, pszVal);
201 : }
202 : }
203 : }
204 :
205 21 : int nNumInstances = 1;
206 21 : if (osGroup.empty())
207 : {
208 48 : auto poNumInstances = poWaterLevel->GetAttribute("numInstances");
209 17 : if (poNumInstances &&
210 17 : poNumInstances->GetDataType().GetClass() == GEDTC_NUMERIC)
211 : {
212 1 : nNumInstances = poNumInstances->ReadAsInt();
213 : }
214 : }
215 21 : if (nNumInstances != 1)
216 : {
217 2 : CPLStringList aosSubDSList;
218 1 : int iSubDS = 0;
219 2 : for (const std::string &featureInstanceName :
220 5 : poWaterLevel->GetGroupNames())
221 : {
222 : auto poFeatureInstance =
223 4 : poWaterLevel->OpenGroup(featureInstanceName);
224 2 : if (poFeatureInstance)
225 : {
226 4 : GDALMajorObject mo;
227 : // Read first vertical datum from root group and let the
228 : // coverage override it.
229 2 : S100ReadVerticalDatum(&mo, poRootGroup.get());
230 2 : S100ReadVerticalDatum(&mo, poFeatureInstance.get());
231 :
232 4 : const auto aosGroupNames = poFeatureInstance->GetGroupNames();
233 4 : for (const auto &osSubGroup : aosGroupNames)
234 : {
235 2 : if (auto poSubGroup =
236 4 : poFeatureInstance->OpenGroup(osSubGroup))
237 : {
238 2 : ++iSubDS;
239 : aosSubDSList.SetNameValue(
240 : CPLSPrintf("SUBDATASET_%d_NAME", iSubDS),
241 : CPLSPrintf("S104:\"%s\":%s:%s", osFilename.c_str(),
242 : featureInstanceName.c_str(),
243 2 : osSubGroup.c_str()));
244 :
245 4 : std::string verticalDatum;
246 : const char *pszValue =
247 2 : mo.GetMetadataItem(S100_VERTICAL_DATUM_MEANING);
248 2 : if (pszValue)
249 : {
250 2 : verticalDatum = ", vertical datum ";
251 2 : verticalDatum += pszValue;
252 : pszValue =
253 2 : mo.GetMetadataItem(S100_VERTICAL_DATUM_ABBREV);
254 2 : if (pszValue)
255 : {
256 2 : verticalDatum += " (";
257 2 : verticalDatum += pszValue;
258 2 : verticalDatum += ')';
259 : }
260 : }
261 : else
262 : {
263 : pszValue =
264 0 : mo.GetMetadataItem(S100_VERTICAL_DATUM_NAME);
265 0 : if (pszValue)
266 : {
267 0 : verticalDatum = ", vertical datum ";
268 0 : verticalDatum += pszValue;
269 : }
270 : }
271 :
272 4 : std::string osSubDSDesc;
273 : const auto poTimePoint =
274 6 : poSubGroup->GetAttribute("timePoint");
275 2 : if (poTimePoint)
276 : {
277 2 : const char *pszVal = poTimePoint->ReadAsString();
278 2 : if (pszVal)
279 : {
280 2 : osSubDSDesc = "Values for feature instance ";
281 2 : osSubDSDesc += featureInstanceName;
282 2 : osSubDSDesc += verticalDatum;
283 2 : osSubDSDesc += " at timestamp ";
284 2 : osSubDSDesc += pszVal;
285 : }
286 : }
287 2 : if (osSubDSDesc.empty())
288 : {
289 0 : osSubDSDesc = "Values for feature instance ";
290 0 : osSubDSDesc += featureInstanceName;
291 0 : osSubDSDesc += verticalDatum;
292 0 : osSubDSDesc += " and group ";
293 0 : osSubDSDesc += osSubGroup;
294 : }
295 :
296 : aosSubDSList.SetNameValue(
297 : CPLSPrintf("SUBDATASET_%d_DESC", iSubDS),
298 2 : osSubDSDesc.c_str());
299 : }
300 : }
301 : }
302 : }
303 :
304 1 : poDS->GDALDataset::SetMetadata(aosSubDSList.List(), "SUBDATASETS");
305 :
306 : // Setup/check for pam .aux.xml.
307 1 : poDS->SetDescription(osFilename.c_str());
308 1 : poDS->TryLoadXML();
309 :
310 : // Setup overviews.
311 1 : poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
312 :
313 1 : return poDS.release();
314 : }
315 :
316 40 : auto poFeatureInstance = poWaterLevel->OpenGroup(osFeatureInstance);
317 20 : if (!poFeatureInstance)
318 : {
319 0 : CPLError(CE_Failure, CPLE_AppDefined,
320 : "Cannot find /WaterLevel/%s group", osFeatureInstance.c_str());
321 0 : return nullptr;
322 : }
323 :
324 : // Read additional metadata
325 100 : for (const char *pszAttrName :
326 : {"timeRecordInterval", "dateTimeOfFirstRecord", "dateTimeOfLastRecord",
327 120 : "numberOfTimes", "dataDynamicity"})
328 : {
329 300 : auto poAttr = poFeatureInstance->GetAttribute(pszAttrName);
330 100 : if (poAttr)
331 : {
332 80 : const char *pszVal = poAttr->ReadAsString();
333 80 : if (pszVal)
334 : {
335 80 : poDS->GDALDataset::SetMetadataItem(pszAttrName, pszVal);
336 : }
337 : }
338 : }
339 :
340 20 : if (auto poDataDynamicity =
341 60 : poFeatureInstance->GetAttribute("dataDynamicity"))
342 : {
343 0 : if (poDataDynamicity->GetDataType().GetClass() == GEDTC_NUMERIC)
344 : {
345 0 : const int nVal = poDataDynamicity->ReadAsInt();
346 : const std::map<int, const char *> oDataDynamicityMap = {
347 : {1, "Observation"},
348 : {2, "Astronomical prediction"},
349 : {3, "Analysis or hybrid method"},
350 : {5, "Hydrodynamic model forecast"},
351 0 : };
352 0 : const auto oIter = oDataDynamicityMap.find(nVal);
353 0 : if (oIter != oDataDynamicityMap.end())
354 0 : poDS->GDALDataset::SetMetadataItem("DATA_DYNAMICITY_MEANING",
355 0 : oIter->second);
356 : }
357 : }
358 :
359 40 : if (auto poStartSequence = poFeatureInstance->GetAttribute("startSequence"))
360 : {
361 20 : const char *pszStartSequence = poStartSequence->ReadAsString();
362 20 : if (pszStartSequence && !EQUAL(pszStartSequence, "0,0"))
363 : {
364 0 : CPLError(CE_Failure, CPLE_AppDefined,
365 : "startSequence (=%s) != 0,0 is not supported",
366 : pszStartSequence);
367 0 : return nullptr;
368 : }
369 : }
370 :
371 20 : if (!S100GetNumPointsLongitudinalLatitudinal(
372 20 : poFeatureInstance.get(), poDS->nRasterXSize, poDS->nRasterYSize))
373 : {
374 0 : return nullptr;
375 : }
376 :
377 : // Potentially override vertical datum
378 20 : S100ReadVerticalDatum(poDS.get(), poFeatureInstance.get());
379 :
380 20 : const bool bNorthUp = CPLTestBool(
381 20 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "NORTH_UP", "YES"));
382 :
383 : // Compute geotransform
384 40 : poDS->m_bHasGT =
385 20 : S100GetGeoTransform(poFeatureInstance.get(), poDS->m_gt, bNorthUp);
386 :
387 20 : if (osGroup.empty())
388 : {
389 30 : const auto aosGroupNames = poFeatureInstance->GetGroupNames();
390 15 : int iSubDS = 1;
391 30 : for (const auto &osSubGroup : aosGroupNames)
392 : {
393 30 : if (auto poSubGroup = poFeatureInstance->OpenGroup(osSubGroup))
394 : {
395 15 : poDS->GDALDataset::SetMetadataItem(
396 : CPLSPrintf("SUBDATASET_%d_NAME", iSubDS),
397 : CPLSPrintf("S104:\"%s\":%s", osFilename.c_str(),
398 : osSubGroup.c_str()),
399 : "SUBDATASETS");
400 30 : std::string osSubDSDesc = "Values for group ";
401 15 : osSubDSDesc += osSubGroup;
402 30 : const auto poTimePoint = poSubGroup->GetAttribute("timePoint");
403 15 : if (poTimePoint)
404 : {
405 15 : const char *pszVal = poTimePoint->ReadAsString();
406 15 : if (pszVal)
407 : {
408 15 : osSubDSDesc = "Values at timestamp ";
409 15 : osSubDSDesc += pszVal;
410 : }
411 : }
412 15 : poDS->GDALDataset::SetMetadataItem(
413 : CPLSPrintf("SUBDATASET_%d_DESC", iSubDS),
414 : osSubDSDesc.c_str(), "SUBDATASETS");
415 15 : ++iSubDS;
416 : }
417 : }
418 : }
419 : else
420 : {
421 5 : auto poGroup = poFeatureInstance->OpenGroup(osGroup);
422 5 : if (!poGroup)
423 : {
424 1 : CPLError(CE_Failure, CPLE_AppDefined,
425 : "Cannot find /WaterLevel/%s/%s group",
426 : osFeatureInstance.c_str(), osGroup.c_str());
427 1 : return nullptr;
428 : }
429 :
430 8 : auto poValuesArray = poGroup->OpenMDArray("values");
431 4 : if (!poValuesArray)
432 : {
433 0 : CPLError(CE_Failure, CPLE_AppDefined,
434 : "Cannot find /WaterLevel/%s/%s/values array",
435 : osFeatureInstance.c_str(), osGroup.c_str());
436 0 : return nullptr;
437 : }
438 :
439 4 : if (poValuesArray->GetDimensionCount() != 2)
440 : {
441 0 : CPLError(CE_Failure, CPLE_AppDefined,
442 : "Wrong dimension count for %s",
443 0 : poValuesArray->GetFullName().c_str());
444 0 : return nullptr;
445 : }
446 :
447 4 : const auto &oType = poValuesArray->GetDataType();
448 4 : if (oType.GetClass() != GEDTC_COMPOUND)
449 : {
450 0 : CPLError(CE_Failure, CPLE_AppDefined, "Wrong data type for %s",
451 0 : poValuesArray->GetFullName().c_str());
452 0 : return nullptr;
453 : }
454 :
455 4 : const auto &oComponents = oType.GetComponents();
456 4 : if (oComponents.size() != 2 ||
457 8 : oComponents[0]->GetName() != "waterLevelHeight" ||
458 4 : oComponents[0]->GetType().GetNumericDataType() != GDT_Float32 ||
459 12 : oComponents[1]->GetName() != "waterLevelTrend" ||
460 4 : (oComponents[1]->GetType().GetNumericDataType() != GDT_UInt8 &&
461 : // In theory should be Byte, but 104US00_ches_dcf2_20190606T12Z.h5 uses Int32
462 0 : oComponents[1]->GetType().GetNumericDataType() != GDT_Int32))
463 : {
464 0 : CPLError(CE_Failure, CPLE_AppDefined, "Wrong data type for %s",
465 0 : poValuesArray->GetFullName().c_str());
466 0 : return nullptr;
467 : }
468 :
469 4 : const auto &apoDims = poValuesArray->GetDimensions();
470 4 : if (apoDims[0]->GetSize() != static_cast<unsigned>(poDS->nRasterYSize))
471 : {
472 0 : CPLError(CE_Failure, CPLE_AppDefined,
473 : "numPointsLatitudinal(=%d) doesn't match first dimension "
474 : "size of %s (=%d)",
475 0 : poDS->nRasterYSize, poValuesArray->GetFullName().c_str(),
476 0 : static_cast<int>(apoDims[0]->GetSize()));
477 0 : return nullptr;
478 : }
479 4 : if (apoDims[1]->GetSize() != static_cast<unsigned>(poDS->nRasterXSize))
480 : {
481 0 : CPLError(CE_Failure, CPLE_AppDefined,
482 : "numPointsLongitudinal(=%d) doesn't match second "
483 : "dimension size of %s (=%d)",
484 0 : poDS->nRasterXSize, poValuesArray->GetFullName().c_str(),
485 0 : static_cast<int>(apoDims[1]->GetSize()));
486 0 : return nullptr;
487 : }
488 :
489 4 : if (bNorthUp)
490 3 : poValuesArray = poValuesArray->GetView("[::-1,...]");
491 :
492 : // Create waterLevelHeight band
493 : auto poWaterLevelHeight =
494 12 : poValuesArray->GetView("[\"waterLevelHeight\"]");
495 : auto poWaterLevelHeightDS = std::unique_ptr<GDALDataset>(
496 8 : poWaterLevelHeight->AsClassicDataset(1, 0));
497 : auto poWaterLevelHeightBand =
498 8 : std::make_unique<S104RasterBand>(std::move(poWaterLevelHeightDS));
499 4 : poWaterLevelHeightBand->SetDescription("waterLevelHeight");
500 4 : poWaterLevelHeightBand->m_osUnitType = "metre";
501 4 : poDS->SetBand(1, poWaterLevelHeightBand.release());
502 :
503 : // Create waterLevelTrend band
504 : auto poWaterLevelTrend =
505 12 : poValuesArray->GetView("[\"waterLevelTrend\"]");
506 : auto poWaterLevelTrendDS = std::unique_ptr<GDALDataset>(
507 8 : poWaterLevelTrend->AsClassicDataset(1, 0));
508 : auto poWaterLevelTrendBand =
509 8 : std::make_unique<S104RasterBand>(std::move(poWaterLevelTrendDS));
510 4 : poWaterLevelTrendBand->SetDescription("waterLevelTrend");
511 :
512 : // From D-5.3 Water Level Trend of S-101 v1.1 spec
513 8 : auto poRAT = std::make_unique<GDALDefaultRasterAttributeTable>();
514 4 : poRAT->CreateColumn("code", GFT_Integer, GFU_MinMax);
515 4 : poRAT->CreateColumn("label", GFT_String, GFU_Generic);
516 4 : poRAT->CreateColumn("definition", GFT_String, GFU_Generic);
517 :
518 : const struct
519 : {
520 : int nCode;
521 : const char *pszLabel;
522 : const char *pszDefinition;
523 4 : } aoRatValues[] = {
524 : {0, "Nodata", "No data"},
525 : {1, "Decreasing", "Becoming smaller in magnitude"},
526 : {2, "Increasing", "Becoming larger in magnitude"},
527 : {3, "Steady", "Constant"},
528 : };
529 :
530 4 : int iRow = 0;
531 20 : for (const auto &oRecord : aoRatValues)
532 : {
533 16 : int iCol = 0;
534 16 : poRAT->SetValue(iRow, iCol++, oRecord.nCode);
535 16 : poRAT->SetValue(iRow, iCol++, oRecord.pszLabel);
536 16 : poRAT->SetValue(iRow, iCol++, oRecord.pszDefinition);
537 16 : ++iRow;
538 : }
539 :
540 4 : poWaterLevelTrendBand->m_poRAT = std::move(poRAT);
541 :
542 4 : poDS->SetBand(2, poWaterLevelTrendBand.release());
543 : }
544 :
545 19 : poDS->GDALDataset::SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT);
546 :
547 : // Setup/check for pam .aux.xml.
548 19 : poDS->SetDescription(osFilename.c_str());
549 19 : poDS->TryLoadXML();
550 :
551 : // Setup overviews.
552 19 : poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
553 :
554 19 : return poDS.release();
555 : }
556 :
557 : /************************************************************************/
558 : /* S104DatasetDriverUnload() */
559 : /************************************************************************/
560 :
561 6 : static void S104DatasetDriverUnload(GDALDriver *)
562 : {
563 6 : HDF5UnloadFileDriver();
564 6 : }
565 :
566 : /************************************************************************/
567 : /* GDALRegister_S104() */
568 : /************************************************************************/
569 11 : void GDALRegister_S104()
570 :
571 : {
572 11 : if (!GDAL_CHECK_VERSION("S104"))
573 0 : return;
574 :
575 11 : if (GDALGetDriverByName(S104_DRIVER_NAME) != nullptr)
576 0 : return;
577 :
578 11 : GDALDriver *poDriver = new GDALDriver();
579 :
580 11 : S104DriverSetCommonMetadata(poDriver);
581 11 : poDriver->pfnOpen = S104Dataset::Open;
582 11 : poDriver->pfnUnloadDriver = S104DatasetDriverUnload;
583 :
584 11 : GetGDALDriverManager()->RegisterDriver(poDriver);
585 : }
|