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-2025, 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 "cpl_time.h"
26 :
27 : #include <algorithm>
28 : #include <array>
29 : #include <cmath>
30 : #include <limits>
31 : #include <map>
32 : #include <variant>
33 :
34 : /************************************************************************/
35 : /* S104Dataset */
36 : /************************************************************************/
37 :
38 188 : class S104Dataset final : public S100BaseDataset
39 : {
40 : public:
41 94 : explicit S104Dataset(const std::string &osFilename)
42 94 : : S100BaseDataset(osFilename)
43 : {
44 94 : }
45 :
46 : ~S104Dataset() override;
47 :
48 : static GDALDataset *Open(GDALOpenInfo *);
49 : static GDALDataset *CreateCopy(const char *pszFilename,
50 : GDALDataset *poSrcDS, int bStrict,
51 : char **papszOptions,
52 : GDALProgressFunc pfnProgress,
53 : void *pProgressData);
54 : };
55 :
56 : S104Dataset::~S104Dataset() = default;
57 :
58 : /************************************************************************/
59 : /* S104RasterBand */
60 : /************************************************************************/
61 :
62 : class S104RasterBand final : public GDALProxyRasterBand
63 : {
64 : friend class S104Dataset;
65 : std::unique_ptr<GDALDataset> m_poDS{};
66 : GDALRasterBand *m_poUnderlyingBand = nullptr;
67 : std::string m_osUnitType{};
68 : std::unique_ptr<GDALRasterAttributeTable> m_poRAT{};
69 :
70 : CPL_DISALLOW_COPY_ASSIGN(S104RasterBand)
71 :
72 : public:
73 71 : explicit S104RasterBand(std::unique_ptr<GDALDataset> &&poDSIn)
74 142 : : m_poDS(std::move(poDSIn)),
75 71 : m_poUnderlyingBand(m_poDS->GetRasterBand(1))
76 : {
77 71 : eDataType = m_poUnderlyingBand->GetRasterDataType();
78 71 : m_poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
79 71 : }
80 :
81 : GDALRasterBand *
82 : RefUnderlyingRasterBand(bool /*bForceOpen*/ = true) const override;
83 :
84 2 : const char *GetUnitType() override
85 : {
86 2 : return m_osUnitType.c_str();
87 : }
88 :
89 1 : GDALRasterAttributeTable *GetDefaultRAT() override
90 : {
91 1 : return m_poRAT.get();
92 : }
93 : };
94 :
95 : GDALRasterBand *
96 44 : S104RasterBand::RefUnderlyingRasterBand(bool /*bForceOpen*/) const
97 : {
98 44 : return m_poUnderlyingBand;
99 : }
100 :
101 : /************************************************************************/
102 : /* Open() */
103 : /************************************************************************/
104 :
105 95 : GDALDataset *S104Dataset::Open(GDALOpenInfo *poOpenInfo)
106 :
107 : {
108 : // Confirm that this appears to be a S104 file.
109 95 : if (!S104DatasetIdentify(poOpenInfo))
110 0 : return nullptr;
111 :
112 : HDF5_GLOBAL_LOCK();
113 :
114 95 : if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
115 : {
116 1 : return HDF5Dataset::OpenMultiDim(poOpenInfo);
117 : }
118 :
119 : // Confirm the requested access is supported.
120 94 : if (poOpenInfo->eAccess == GA_Update)
121 : {
122 0 : ReportUpdateNotSupportedByDriver("S104");
123 0 : return nullptr;
124 : }
125 :
126 188 : std::string osFilename(poOpenInfo->pszFilename);
127 188 : std::string osFeatureInstance = "WaterLevel.01";
128 188 : std::string osGroup;
129 94 : if (STARTS_WITH(poOpenInfo->pszFilename, "S104:"))
130 : {
131 : const CPLStringList aosTokens(
132 38 : CSLTokenizeString2(poOpenInfo->pszFilename, ":",
133 38 : CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES));
134 :
135 38 : if (aosTokens.size() == 2)
136 : {
137 2 : osFilename = aosTokens[1];
138 : }
139 36 : else if (aosTokens.size() == 3)
140 : {
141 28 : osFilename = aosTokens[1];
142 28 : osGroup = aosTokens[2];
143 : }
144 8 : else if (aosTokens.size() == 4)
145 : {
146 8 : osFilename = aosTokens[1];
147 8 : osFeatureInstance = aosTokens[2];
148 8 : osGroup = aosTokens[3];
149 : }
150 : else
151 : {
152 0 : return nullptr;
153 : }
154 : }
155 :
156 188 : auto poDS = std::make_unique<S104Dataset>(osFilename);
157 94 : if (!poDS->Init())
158 0 : return nullptr;
159 :
160 94 : const auto &poRootGroup = poDS->m_poRootGroup;
161 :
162 282 : auto poVerticalCS = poRootGroup->GetAttribute("verticalCS");
163 94 : if (poVerticalCS && poVerticalCS->GetDataType().GetClass() == GEDTC_NUMERIC)
164 : {
165 89 : const int nVerticalCS = poVerticalCS->ReadAsInt();
166 89 : if (nVerticalCS == 6498)
167 89 : poDS->GDALDataset::SetMetadataItem(
168 : "VERTICAL_CS_DEFINITION", "depth, meters, orientation down");
169 0 : else if (nVerticalCS == 6499)
170 0 : poDS->GDALDataset::SetMetadataItem(
171 : "VERTICAL_CS_DEFINITION", "height, meters, orientation up");
172 :
173 178 : poDS->GDALDataset::SetMetadataItem("verticalCS",
174 178 : std::to_string(nVerticalCS).c_str());
175 : }
176 :
177 282 : auto poWaterLevel = poRootGroup->OpenGroup("WaterLevel");
178 94 : if (!poWaterLevel)
179 : {
180 7 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find /WaterLevel group");
181 7 : return nullptr;
182 : }
183 :
184 261 : auto poDataCodingFormat = poWaterLevel->GetAttribute("dataCodingFormat");
185 174 : if (!poDataCodingFormat ||
186 87 : poDataCodingFormat->GetDataType().GetClass() != GEDTC_NUMERIC)
187 : {
188 0 : CPLError(CE_Failure, CPLE_AppDefined,
189 : "Cannot find /WaterLevel/dataCodingFormat attribute");
190 0 : return nullptr;
191 : }
192 87 : const int nDataCodingFormat = poDataCodingFormat->ReadAsInt();
193 87 : if (nDataCodingFormat != 2)
194 : {
195 0 : CPLError(CE_Failure, CPLE_NotSupported,
196 : "dataCodingFormat=%d is not supported by the S104 driver",
197 : nDataCodingFormat);
198 0 : return nullptr;
199 : }
200 :
201 : // Read additional metadata
202 696 : for (const char *pszAttrName :
203 : {"methodWaterLevelProduct", "minDatasetHeight", "maxDatasetHeight",
204 : "horizontalPositionUncertainty", "verticalUncertainty",
205 783 : "timeUncertainty", "commonPointRule", "interpolationType"})
206 : {
207 2088 : auto poAttr = poWaterLevel->GetAttribute(pszAttrName);
208 696 : if (poAttr)
209 : {
210 442 : const char *pszVal = poAttr->ReadAsString();
211 442 : if (pszVal)
212 : {
213 442 : poDS->GDALDataset::SetMetadataItem(pszAttrName, pszVal);
214 : }
215 : }
216 : }
217 :
218 243 : if (auto poCommonPointRule = poWaterLevel->GetAttribute("commonPointRule"))
219 : {
220 69 : poDS->SetMetadataForCommonPointRule(poCommonPointRule.get());
221 : }
222 :
223 87 : if (auto poInterpolationType =
224 261 : poWaterLevel->GetAttribute("interpolationType"))
225 : {
226 69 : poDS->SetMetadataForInterpolationType(poInterpolationType.get());
227 : }
228 :
229 87 : int nNumInstances = 1;
230 87 : if (osGroup.empty())
231 : {
232 153 : auto poNumInstances = poWaterLevel->GetAttribute("numInstances");
233 87 : if (poNumInstances &&
234 87 : poNumInstances->GetDataType().GetClass() == GEDTC_NUMERIC)
235 : {
236 36 : nNumInstances = poNumInstances->ReadAsInt();
237 : }
238 : }
239 87 : if (nNumInstances != 1)
240 : {
241 8 : CPLStringList aosSubDSList;
242 4 : int iSubDS = 0;
243 8 : for (const std::string &featureInstanceName :
244 20 : poWaterLevel->GetGroupNames())
245 : {
246 : auto poFeatureInstance =
247 16 : poWaterLevel->OpenGroup(featureInstanceName);
248 8 : if (poFeatureInstance)
249 : {
250 16 : GDALMajorObject mo;
251 : // Read first vertical datum from root group and let the
252 : // coverage override it.
253 8 : S100ReadVerticalDatum(&mo, poRootGroup.get());
254 8 : S100ReadVerticalDatum(&mo, poFeatureInstance.get());
255 :
256 16 : const auto aosGroupNames = poFeatureInstance->GetGroupNames();
257 16 : for (const auto &osSubGroup : aosGroupNames)
258 : {
259 8 : if (auto poSubGroup =
260 16 : poFeatureInstance->OpenGroup(osSubGroup))
261 : {
262 8 : ++iSubDS;
263 : aosSubDSList.SetNameValue(
264 : CPLSPrintf("SUBDATASET_%d_NAME", iSubDS),
265 : CPLSPrintf("S104:\"%s\":%s:%s", osFilename.c_str(),
266 : featureInstanceName.c_str(),
267 8 : osSubGroup.c_str()));
268 :
269 16 : std::string verticalDatum;
270 : const char *pszValue =
271 8 : mo.GetMetadataItem(S100_VERTICAL_DATUM_NAME);
272 8 : if (pszValue)
273 : {
274 8 : verticalDatum = ", vertical datum ";
275 8 : verticalDatum += pszValue;
276 : pszValue =
277 8 : mo.GetMetadataItem(S100_VERTICAL_DATUM_ABBREV);
278 8 : if (pszValue)
279 : {
280 5 : verticalDatum += " (";
281 5 : verticalDatum += pszValue;
282 5 : verticalDatum += ')';
283 : }
284 : }
285 :
286 16 : std::string osSubDSDesc;
287 : const auto poTimePoint =
288 24 : poSubGroup->GetAttribute("timePoint");
289 8 : if (poTimePoint)
290 : {
291 8 : const char *pszVal = poTimePoint->ReadAsString();
292 8 : if (pszVal)
293 : {
294 8 : osSubDSDesc = "Values for feature instance ";
295 8 : osSubDSDesc += featureInstanceName;
296 8 : osSubDSDesc += verticalDatum;
297 8 : osSubDSDesc += " at timestamp ";
298 8 : osSubDSDesc += pszVal;
299 : }
300 : }
301 8 : if (osSubDSDesc.empty())
302 : {
303 0 : osSubDSDesc = "Values for feature instance ";
304 0 : osSubDSDesc += featureInstanceName;
305 0 : osSubDSDesc += verticalDatum;
306 0 : osSubDSDesc += " and group ";
307 0 : osSubDSDesc += osSubGroup;
308 : }
309 :
310 : aosSubDSList.SetNameValue(
311 : CPLSPrintf("SUBDATASET_%d_DESC", iSubDS),
312 8 : osSubDSDesc.c_str());
313 : }
314 : }
315 : }
316 : }
317 :
318 4 : poDS->GDALDataset::SetMetadata(aosSubDSList.List(), "SUBDATASETS");
319 :
320 : // Setup/check for pam .aux.xml.
321 4 : poDS->SetDescription(osFilename.c_str());
322 4 : poDS->TryLoadXML();
323 :
324 : // Setup overviews.
325 4 : poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
326 :
327 4 : return poDS.release();
328 : }
329 :
330 166 : auto poFeatureInstance = poWaterLevel->OpenGroup(osFeatureInstance);
331 83 : if (!poFeatureInstance)
332 : {
333 0 : CPLError(CE_Failure, CPLE_AppDefined,
334 : "Cannot find /WaterLevel/%s group", osFeatureInstance.c_str());
335 0 : return nullptr;
336 : }
337 :
338 : // Read additional metadata
339 415 : for (const char *pszAttrName :
340 : {"timeRecordInterval", "dateTimeOfFirstRecord", "dateTimeOfLastRecord",
341 498 : "numberOfTimes", "dataDynamicity"})
342 : {
343 1245 : auto poAttr = poFeatureInstance->GetAttribute(pszAttrName);
344 415 : if (poAttr)
345 : {
346 340 : const char *pszVal = poAttr->ReadAsString();
347 340 : if (pszVal)
348 : {
349 340 : poDS->GDALDataset::SetMetadataItem(pszAttrName, pszVal);
350 : }
351 : }
352 : }
353 :
354 83 : if (auto poDataDynamicity =
355 249 : poFeatureInstance->GetAttribute("dataDynamicity"))
356 : {
357 59 : poDS->SetMetadataForDataDynamicity(poDataDynamicity.get());
358 : }
359 :
360 166 : if (auto poStartSequence = poFeatureInstance->GetAttribute("startSequence"))
361 : {
362 83 : const char *pszStartSequence = poStartSequence->ReadAsString();
363 83 : if (pszStartSequence && !EQUAL(pszStartSequence, "0,0"))
364 : {
365 0 : CPLError(CE_Failure, CPLE_AppDefined,
366 : "startSequence (=%s) != 0,0 is not supported",
367 : pszStartSequence);
368 0 : return nullptr;
369 : }
370 : }
371 :
372 83 : if (!S100GetNumPointsLongitudinalLatitudinal(
373 83 : poFeatureInstance.get(), poDS->nRasterXSize, poDS->nRasterYSize))
374 : {
375 0 : return nullptr;
376 : }
377 :
378 : // Potentially override vertical datum
379 83 : S100ReadVerticalDatum(poDS.get(), poFeatureInstance.get());
380 :
381 83 : const bool bNorthUp = CPLTestBool(
382 83 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "NORTH_UP", "YES"));
383 :
384 : // Compute geotransform
385 166 : poDS->m_bHasGT =
386 83 : S100GetGeoTransform(poFeatureInstance.get(), poDS->m_gt, bNorthUp);
387 :
388 83 : if (osGroup.empty())
389 : {
390 94 : const auto aosGroupNames = poFeatureInstance->GetGroupNames();
391 47 : int iSubDS = 1;
392 96 : for (const auto &osSubGroup : aosGroupNames)
393 : {
394 98 : if (auto poSubGroup = poFeatureInstance->OpenGroup(osSubGroup))
395 : {
396 49 : poDS->GDALDataset::SetMetadataItem(
397 : CPLSPrintf("SUBDATASET_%d_NAME", iSubDS),
398 : CPLSPrintf("S104:\"%s\":%s", osFilename.c_str(),
399 : osSubGroup.c_str()),
400 : "SUBDATASETS");
401 98 : std::string osSubDSDesc = "Values for group ";
402 49 : osSubDSDesc += osSubGroup;
403 98 : const auto poTimePoint = poSubGroup->GetAttribute("timePoint");
404 49 : if (poTimePoint)
405 : {
406 49 : const char *pszVal = poTimePoint->ReadAsString();
407 49 : if (pszVal)
408 : {
409 49 : osSubDSDesc = "Values at timestamp ";
410 49 : osSubDSDesc += pszVal;
411 : }
412 : }
413 49 : poDS->GDALDataset::SetMetadataItem(
414 : CPLSPrintf("SUBDATASET_%d_DESC", iSubDS),
415 : osSubDSDesc.c_str(), "SUBDATASETS");
416 49 : ++iSubDS;
417 : }
418 : }
419 : }
420 : else
421 : {
422 36 : auto poGroup = poFeatureInstance->OpenGroup(osGroup);
423 36 : if (!poGroup)
424 : {
425 1 : CPLError(CE_Failure, CPLE_AppDefined,
426 : "Cannot find /WaterLevel/%s/%s group",
427 : osFeatureInstance.c_str(), osGroup.c_str());
428 1 : return nullptr;
429 : }
430 :
431 : // Read additional metadata
432 105 : for (const char *pszAttrName :
433 140 : {"timePoint", "waterLevelTrendThreshold", "trendInterval"})
434 : {
435 315 : auto poAttr = poGroup->GetAttribute(pszAttrName);
436 105 : if (poAttr)
437 : {
438 35 : const char *pszVal = poAttr->ReadAsString();
439 35 : if (pszVal)
440 : {
441 35 : poDS->GDALDataset::SetMetadataItem(pszAttrName, pszVal);
442 : }
443 : }
444 : }
445 :
446 70 : auto poValuesArray = poGroup->OpenMDArray("values");
447 35 : if (!poValuesArray)
448 : {
449 0 : CPLError(CE_Failure, CPLE_AppDefined,
450 : "Cannot find /WaterLevel/%s/%s/values array",
451 : osFeatureInstance.c_str(), osGroup.c_str());
452 0 : return nullptr;
453 : }
454 :
455 35 : if (poValuesArray->GetDimensionCount() != 2)
456 : {
457 0 : CPLError(CE_Failure, CPLE_AppDefined,
458 : "Wrong dimension count for %s",
459 0 : poValuesArray->GetFullName().c_str());
460 0 : return nullptr;
461 : }
462 :
463 35 : const auto &oType = poValuesArray->GetDataType();
464 35 : if (oType.GetClass() != GEDTC_COMPOUND)
465 : {
466 0 : CPLError(CE_Failure, CPLE_AppDefined, "Wrong data type for %s",
467 0 : poValuesArray->GetFullName().c_str());
468 0 : return nullptr;
469 : }
470 :
471 35 : const auto &oComponents = oType.GetComponents();
472 36 : if ((oComponents.size() != 2 && oComponents.size() != 3) ||
473 70 : oComponents[0]->GetName() != "waterLevelHeight" ||
474 35 : oComponents[0]->GetType().GetNumericDataType() != GDT_Float32 ||
475 70 : oComponents[1]->GetName() != "waterLevelTrend" ||
476 35 : (oComponents[1]->GetType().GetNumericDataType() != GDT_UInt8 &&
477 : // In theory should be Byte, but 104US00_ches_dcf2_20190606T12Z.h5 uses Int32
478 71 : oComponents[1]->GetType().GetNumericDataType() != GDT_Int32) ||
479 35 : (oComponents.size() == 3 &&
480 2 : (oComponents[2]->GetName() != "uncertainty" ||
481 1 : oComponents[2]->GetType().GetNumericDataType() != GDT_Float32)))
482 : {
483 0 : CPLError(CE_Failure, CPLE_AppDefined, "Wrong data type for %s",
484 0 : poValuesArray->GetFullName().c_str());
485 0 : return nullptr;
486 : }
487 :
488 35 : const auto &apoDims = poValuesArray->GetDimensions();
489 35 : if (apoDims[0]->GetSize() != static_cast<unsigned>(poDS->nRasterYSize))
490 : {
491 0 : CPLError(CE_Failure, CPLE_AppDefined,
492 : "numPointsLatitudinal(=%d) doesn't match first dimension "
493 : "size of %s (=%d)",
494 0 : poDS->nRasterYSize, poValuesArray->GetFullName().c_str(),
495 0 : static_cast<int>(apoDims[0]->GetSize()));
496 0 : return nullptr;
497 : }
498 35 : if (apoDims[1]->GetSize() != static_cast<unsigned>(poDS->nRasterXSize))
499 : {
500 0 : CPLError(CE_Failure, CPLE_AppDefined,
501 : "numPointsLongitudinal(=%d) doesn't match second "
502 : "dimension size of %s (=%d)",
503 0 : poDS->nRasterXSize, poValuesArray->GetFullName().c_str(),
504 0 : static_cast<int>(apoDims[1]->GetSize()));
505 0 : return nullptr;
506 : }
507 :
508 35 : if (bNorthUp)
509 34 : poValuesArray = poValuesArray->GetView("[::-1,...]");
510 :
511 : // Create waterLevelHeight band
512 : auto poWaterLevelHeight =
513 105 : poValuesArray->GetView("[\"waterLevelHeight\"]");
514 : auto poWaterLevelHeightDS = std::unique_ptr<GDALDataset>(
515 70 : poWaterLevelHeight->AsClassicDataset(1, 0));
516 : auto poWaterLevelHeightBand =
517 70 : std::make_unique<S104RasterBand>(std::move(poWaterLevelHeightDS));
518 35 : poWaterLevelHeightBand->SetDescription("waterLevelHeight");
519 35 : poWaterLevelHeightBand->m_osUnitType = "metre";
520 35 : poDS->SetBand(1, poWaterLevelHeightBand.release());
521 :
522 : // Create waterLevelTrend band
523 : auto poWaterLevelTrend =
524 105 : poValuesArray->GetView("[\"waterLevelTrend\"]");
525 : auto poWaterLevelTrendDS = std::unique_ptr<GDALDataset>(
526 70 : poWaterLevelTrend->AsClassicDataset(1, 0));
527 : auto poWaterLevelTrendBand =
528 70 : std::make_unique<S104RasterBand>(std::move(poWaterLevelTrendDS));
529 35 : poWaterLevelTrendBand->SetDescription("waterLevelTrend");
530 :
531 : // From D-5.3 Water Level Trend of S-101 v1.1 spec
532 70 : auto poRAT = std::make_unique<GDALDefaultRasterAttributeTable>();
533 35 : poRAT->CreateColumn("code", GFT_Integer, GFU_MinMax);
534 35 : poRAT->CreateColumn("label", GFT_String, GFU_Generic);
535 35 : poRAT->CreateColumn("definition", GFT_String, GFU_Generic);
536 :
537 : const struct
538 : {
539 : int nCode;
540 : const char *pszLabel;
541 : const char *pszDefinition;
542 35 : } aoRatValues[] = {
543 : {0, "Nodata", "No data"},
544 : {1, "Decreasing", "Becoming smaller in magnitude"},
545 : {2, "Increasing", "Becoming larger in magnitude"},
546 : {3, "Steady", "Constant"},
547 : };
548 :
549 35 : int iRow = 0;
550 175 : for (const auto &oRecord : aoRatValues)
551 : {
552 140 : int iCol = 0;
553 140 : poRAT->SetValue(iRow, iCol++, oRecord.nCode);
554 140 : poRAT->SetValue(iRow, iCol++, oRecord.pszLabel);
555 140 : poRAT->SetValue(iRow, iCol++, oRecord.pszDefinition);
556 140 : ++iRow;
557 : }
558 :
559 35 : poWaterLevelTrendBand->m_poRAT = std::move(poRAT);
560 :
561 35 : poDS->SetBand(2, poWaterLevelTrendBand.release());
562 :
563 35 : if (oComponents.size() == 3)
564 : {
565 : // Create uncertainty band
566 : auto poUncertaintyArray =
567 3 : poValuesArray->GetView("[\"uncertainty\"]");
568 : auto poUncertaintyDS = std::unique_ptr<GDALDataset>(
569 2 : poUncertaintyArray->AsClassicDataset(1, 0));
570 : auto poUncertaintyBand =
571 2 : std::make_unique<S104RasterBand>(std::move(poUncertaintyDS));
572 1 : poUncertaintyBand->SetDescription("uncertainty");
573 1 : poUncertaintyBand->m_osUnitType = "metre";
574 1 : poDS->SetBand(3, poUncertaintyBand.release());
575 : }
576 :
577 : auto poUncertaintyDataset =
578 105 : poFeatureInstance->OpenMDArray("uncertainty");
579 35 : if (poUncertaintyDataset)
580 : {
581 : const auto &apoUncertaintyDims =
582 30 : poUncertaintyDataset->GetDimensions();
583 30 : const auto &oUncertaintyType = poUncertaintyDataset->GetDataType();
584 60 : if (apoUncertaintyDims.size() == 1 &&
585 60 : apoUncertaintyDims[0]->GetSize() == 1 &&
586 30 : oUncertaintyType.GetClass() == GEDTC_COMPOUND)
587 : {
588 : const auto &oUncertaintyComponents =
589 30 : oUncertaintyType.GetComponents();
590 60 : if (oUncertaintyComponents.size() == 2 &&
591 30 : oUncertaintyComponents[1]->GetType().GetClass() ==
592 : GEDTC_NUMERIC)
593 : {
594 : auto poView = poUncertaintyDataset->GetView(
595 60 : std::string("[\"")
596 30 : .append(oUncertaintyComponents[1]->GetName())
597 90 : .append("\"]"));
598 30 : double dfVal = 0;
599 30 : const GUInt64 arrayStartIdx[] = {0};
600 30 : const size_t count[] = {1};
601 30 : const GInt64 arrayStep[] = {0};
602 30 : const GPtrDiff_t bufferStride[] = {0};
603 60 : if (poView &&
604 60 : poView->Read(
605 : arrayStartIdx, count, arrayStep, bufferStride,
606 90 : GDALExtendedDataType::Create(GDT_Float64), &dfVal))
607 : {
608 30 : poDS->GDALDataset::SetMetadataItem(
609 : "uncertainty", CPLSPrintf("%f", dfVal));
610 : }
611 : }
612 : }
613 : }
614 : }
615 :
616 82 : poDS->GDALDataset::SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT);
617 :
618 : // Setup/check for pam .aux.xml.
619 82 : if (osFilename != poOpenInfo->pszFilename)
620 : {
621 37 : poDS->SetSubdatasetName((osFeatureInstance + "/" + osGroup).c_str());
622 37 : poDS->SetPhysicalFilename(osFilename.c_str());
623 : }
624 82 : poDS->SetDescription(poOpenInfo->pszFilename);
625 82 : poDS->TryLoadXML();
626 :
627 : // Setup overviews.
628 82 : poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
629 :
630 82 : return poDS.release();
631 : }
632 :
633 : /************************************************************************/
634 : /* S104Creator */
635 : /************************************************************************/
636 :
637 : class S104Creator final : public S100BaseWriter
638 : {
639 : public:
640 75 : S104Creator(const char *pszDestFilename, GDALDataset *poSrcDS,
641 : CSLConstList papszOptions)
642 75 : : S100BaseWriter(pszDestFilename, poSrcDS, papszOptions)
643 : {
644 75 : }
645 :
646 : ~S104Creator() override;
647 :
648 : bool Create(GDALProgressFunc pfnProgress, void *pProgressData);
649 :
650 : static constexpr const char *FEATURE_TYPE = "WaterLevel";
651 :
652 : protected:
653 109 : bool Close() override
654 : {
655 109 : return BaseClose();
656 : }
657 :
658 : private:
659 : bool WriteFeatureGroupAttributes();
660 : bool WriteUncertaintyDataset();
661 : bool FillFeatureInstanceGroup(
662 : const std::map<std::string, std::variant<GDALDataset *, std::string>>
663 : &oMapTimestampToDS,
664 : GDALProgressFunc pfnProgress, void *pProgressData);
665 : bool CopyValues(GDALDataset *poSrcDS, GDALProgressFunc pfnProgress,
666 : void *pProgressData);
667 : bool CreateGroupF();
668 : };
669 :
670 : /************************************************************************/
671 : /* S104Creator::~S104Creator() */
672 : /************************************************************************/
673 :
674 75 : S104Creator::~S104Creator()
675 : {
676 75 : S104Creator::Close();
677 75 : }
678 :
679 : /************************************************************************/
680 : /* S104Creator::Create() */
681 : /************************************************************************/
682 :
683 75 : bool S104Creator::Create(GDALProgressFunc pfnProgress, void *pProgressData)
684 : {
685 : CPLStringList aosDatasets(
686 150 : CSLTokenizeString2(m_aosOptions.FetchNameValue("DATASETS"), ",", 0));
687 75 : if (m_poSrcDS->GetRasterCount() == 0 && aosDatasets.empty())
688 : {
689 : // Deal with S104 -> S104 translation;
690 3 : CSLConstList papszSubdatasets = m_poSrcDS->GetMetadata("SUBDATASETS");
691 3 : if (papszSubdatasets)
692 : {
693 2 : int iSubDS = 0;
694 2 : std::string osFirstDataset;
695 2 : std::string osDatasets;
696 20 : for (const auto &[pszItem, pszValue] :
697 22 : cpl::IterateNameValue(papszSubdatasets))
698 : {
699 30 : if (STARTS_WITH(pszItem, "SUBDATASET_") &&
700 15 : cpl::ends_with(std::string_view(pszItem), "_NAME") &&
701 5 : STARTS_WITH(pszValue, "S104:"))
702 : {
703 5 : if (strstr(pszValue, ":WaterLevel."))
704 : {
705 : auto poTmpDS =
706 : std::unique_ptr<GDALDataset>(GDALDataset::Open(
707 : pszValue,
708 2 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
709 2 : if (!poTmpDS)
710 0 : return false;
711 2 : CPLStringList aosOptions(m_aosOptions);
712 2 : if (iSubDS > 0)
713 1 : aosOptions.SetNameValue("APPEND_SUBDATASET", "YES");
714 : S104Creator oAuxCreator(m_osDestFilename.c_str(),
715 : poTmpDS.get(),
716 2 : aosOptions.List());
717 : const int nSubDSCount =
718 2 : ((CSLCount(papszSubdatasets) + 1) / 2);
719 : std::unique_ptr<void,
720 : decltype(&GDALDestroyScaledProgress)>
721 : pScaledProgressData(
722 : GDALCreateScaledProgress(
723 2 : static_cast<double>(iSubDS) / nSubDSCount,
724 2 : static_cast<double>(iSubDS + 1) /
725 : nSubDSCount,
726 : pfnProgress, pProgressData),
727 2 : GDALDestroyScaledProgress);
728 2 : ++iSubDS;
729 2 : if (!oAuxCreator.Create(GDALScaledProgress,
730 : pScaledProgressData.get()))
731 0 : return false;
732 : }
733 : else
734 : {
735 3 : if (osFirstDataset.empty())
736 1 : osFirstDataset = pszValue;
737 3 : if (!osDatasets.empty())
738 2 : osDatasets += ',';
739 3 : osDatasets += pszValue;
740 : }
741 : }
742 : }
743 2 : if (iSubDS > 0)
744 : {
745 1 : return true;
746 : }
747 1 : else if (!osDatasets.empty())
748 : {
749 : auto poTmpDS = std::unique_ptr<GDALDataset>(
750 : GDALDataset::Open(osFirstDataset.c_str(),
751 2 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
752 1 : if (!poTmpDS)
753 0 : return false;
754 2 : CPLStringList aosOptions(m_aosOptions);
755 1 : aosOptions.SetNameValue("DATASETS", osDatasets.c_str());
756 : S104Creator oAuxCreator(m_osDestFilename.c_str(), poTmpDS.get(),
757 2 : aosOptions.List());
758 1 : return oAuxCreator.Create(pfnProgress, pProgressData);
759 : }
760 : }
761 : }
762 :
763 73 : if (m_poSrcDS->GetRasterCount() != 2 && m_poSrcDS->GetRasterCount() != 3)
764 : {
765 17 : CPLError(CE_Failure, CPLE_NotSupported,
766 : "Source dataset %s must have two or three bands",
767 17 : m_poSrcDS->GetDescription());
768 17 : return false;
769 : }
770 :
771 56 : if (!BaseChecks("S104", /* crsMustBeEPSG = */ false,
772 : /* verticalDatumRequired = */ true))
773 9 : return false;
774 :
775 : std::map<std::string, std::variant<GDALDataset *, std::string>>
776 94 : oMapTimestampToDS;
777 : CPLStringList aosDatasetsTimePoint(CSLTokenizeString2(
778 94 : m_aosOptions.FetchNameValue("DATASETS_TIME_POINT"), ",", 0));
779 47 : if (!aosDatasets.empty())
780 : {
781 11 : if (!aosDatasetsTimePoint.empty() &&
782 3 : aosDatasetsTimePoint.size() != aosDatasets.size())
783 : {
784 1 : CPLError(CE_Failure, CPLE_AppDefined,
785 : "DATASETS_TIME_POINT does not have the same number of "
786 : "values as DATASETS");
787 1 : return false;
788 : }
789 7 : int i = 0;
790 14 : for (const char *pszDataset : aosDatasets)
791 : {
792 : auto poDS = std::unique_ptr<GDALDataset>(GDALDataset::Open(
793 11 : pszDataset, GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
794 11 : if (!poDS)
795 1 : return false;
796 19 : if (poDS->GetRasterXSize() != m_poSrcDS->GetRasterXSize() ||
797 9 : poDS->GetRasterYSize() != m_poSrcDS->GetRasterYSize())
798 : {
799 1 : CPLError(CE_Failure, CPLE_NotSupported,
800 : "Dataset %s does not have the same dimensions as %s",
801 1 : poDS->GetDescription(), m_poSrcDS->GetDescription());
802 1 : return false;
803 : }
804 9 : if (poDS->GetRasterCount() != m_poSrcDS->GetRasterCount())
805 : {
806 0 : CPLError(CE_Failure, CPLE_NotSupported,
807 : "Dataset %s must have %d bands",
808 0 : poDS->GetDescription(), m_poSrcDS->GetRasterCount());
809 0 : return false;
810 : }
811 9 : auto poSRS = poDS->GetSpatialRef();
812 9 : if (!poSRS || !poSRS->IsSame(m_poSRS))
813 : {
814 0 : CPLError(CE_Failure, CPLE_NotSupported,
815 : "Dataset %s does not have the same CRS as %s",
816 0 : poDS->GetDescription(), m_poSrcDS->GetDescription());
817 0 : return false;
818 : }
819 9 : GDALGeoTransform gt;
820 9 : if (poDS->GetGeoTransform(gt) != CE_None || gt != m_gt)
821 : {
822 0 : CPLError(CE_Failure, CPLE_NotSupported,
823 : "Dataset %s does not have the same geotransform as %s",
824 0 : poDS->GetDescription(), m_poSrcDS->GetDescription());
825 0 : return false;
826 : }
827 : const char *pszVerticalDatum =
828 9 : poDS->GetMetadataItem("VERTICAL_DATUM");
829 9 : if (pszVerticalDatum)
830 : {
831 : const int nVerticalDatum =
832 0 : S100GetVerticalDatumCodeFromNameOrAbbrev(pszVerticalDatum);
833 0 : if (nVerticalDatum != m_nVerticalDatum)
834 : {
835 0 : CPLError(CE_Failure, CPLE_NotSupported,
836 : "Dataset %s does not have the same vertical datum "
837 : "as %s",
838 0 : poDS->GetDescription(),
839 0 : m_poSrcDS->GetDescription());
840 0 : return false;
841 : }
842 : }
843 9 : const char *pszTimePoint = poDS->GetMetadataItem("timePoint");
844 9 : if (!pszTimePoint && !aosDatasetsTimePoint.empty())
845 2 : pszTimePoint = aosDatasetsTimePoint[i];
846 9 : if (!pszTimePoint)
847 : {
848 1 : CPLError(
849 : CE_Failure, CPLE_NotSupported,
850 : "Dataset %s does not have a timePoint metadata item, and "
851 : "the DATASETS_TIME_POINT creation option is not set",
852 1 : poDS->GetDescription());
853 1 : return false;
854 : }
855 8 : if (strlen(pszTimePoint) != strlen("YYYYMMDDTHHMMSSZ") ||
856 7 : pszTimePoint[8] != 'T' || pszTimePoint[15] != 'Z')
857 : {
858 1 : CPLError(CE_Failure, CPLE_AppDefined,
859 : "timePoint value for dataset %s is %s, but does not "
860 : "conform to a YYYYMMDDTHHMMSSZ datetime value.",
861 1 : poDS->GetDescription(), pszTimePoint);
862 1 : return false;
863 : }
864 7 : if (cpl::contains(oMapTimestampToDS, pszTimePoint))
865 : {
866 0 : CPLError(CE_Failure, CPLE_AppDefined,
867 : "Several datasets are at timePoint %s.", pszTimePoint);
868 0 : return false;
869 : }
870 7 : oMapTimestampToDS[pszTimePoint] = pszDataset;
871 7 : ++i;
872 : }
873 : }
874 :
875 : {
876 42 : const char *pszTimePoint = m_aosOptions.FetchNameValueDef(
877 42 : "TIME_POINT", m_poSrcDS->GetMetadataItem("timePoint"));
878 42 : if (!pszTimePoint)
879 : {
880 1 : CPLError(CE_Failure, CPLE_AppDefined,
881 : "TIME_POINT creation option value must "
882 : "be set, or source dataset must have a timePoint metadata "
883 : "item.");
884 1 : return false;
885 : }
886 41 : if (strlen(pszTimePoint) != strlen("YYYYMMDDTHHMMSSZ") ||
887 40 : pszTimePoint[8] != 'T' || pszTimePoint[15] != 'Z')
888 : {
889 1 : CPLError(CE_Failure, CPLE_AppDefined,
890 : "TIME_POINT creation option value must "
891 : "be set to a YYYYMMDDTHHMMSSZ datetime value.");
892 1 : return false;
893 : }
894 :
895 40 : if (oMapTimestampToDS.empty())
896 : {
897 37 : oMapTimestampToDS[pszTimePoint] = m_poSrcDS;
898 : }
899 : else
900 : {
901 3 : const auto oIter = oMapTimestampToDS.find(pszTimePoint);
902 6 : if (oIter != oMapTimestampToDS.end() &&
903 6 : CPLString(std::get<std::string>(oIter->second))
904 3 : .replaceAll('\\', '/') !=
905 6 : CPLString(m_poSrcDS->GetDescription())
906 3 : .replaceAll('\\', '/'))
907 : {
908 1 : CPLError(CE_Failure, CPLE_AppDefined,
909 : "Several datasets are at timePoint %s (%s vs %s).",
910 : pszTimePoint,
911 1 : std::get<std::string>(oIter->second).c_str(),
912 1 : m_poSrcDS->GetDescription());
913 1 : return false;
914 : }
915 : }
916 : }
917 39 : if (oMapTimestampToDS.size() > 999)
918 : {
919 0 : CPLError(
920 : CE_Failure, CPLE_AppDefined,
921 : "Only up to 999 datasets are supported for a same vertical datum");
922 0 : return false;
923 : }
924 :
925 77 : if (m_poSRS->IsVertical() || m_poSRS->IsCompound() || m_poSRS->IsLocal() ||
926 38 : m_poSRS->GetAxesCount() != 2)
927 : {
928 1 : CPLError(CE_Failure, CPLE_NotSupported,
929 : "The CRS must be a geographic 2D or projected 2D CRS");
930 1 : return false;
931 : }
932 :
933 : const bool bAppendSubdataset =
934 38 : CPLTestBool(m_aosOptions.FetchNameValueDef("APPEND_SUBDATASET", "NO"));
935 38 : if (bAppendSubdataset)
936 : {
937 4 : GDALOpenInfo oOpenInfo(m_osDestFilename.c_str(), GA_ReadOnly);
938 : auto poOriDS =
939 4 : std::unique_ptr<GDALDataset>(S104Dataset::Open(&oOpenInfo));
940 2 : if (!poOriDS)
941 : {
942 0 : CPLError(CE_Failure, CPLE_AppDefined,
943 : "%s is not a valid existing S104 dataset",
944 : m_osDestFilename.c_str());
945 0 : return false;
946 : }
947 2 : const auto poOriSRS = poOriDS->GetSpatialRef();
948 2 : if (!poOriSRS)
949 : {
950 : // shouldn't happen
951 0 : return false;
952 : }
953 2 : if (!poOriSRS->IsSame(m_poSRS))
954 : {
955 0 : CPLError(CE_Failure, CPLE_AppDefined,
956 : "CRS of %s is not the same as the one of %s",
957 0 : m_osDestFilename.c_str(), m_poSrcDS->GetDescription());
958 0 : return false;
959 : }
960 2 : poOriDS.reset();
961 :
962 2 : OGREnvelope sExtent;
963 2 : if (m_poSrcDS->GetExtentWGS84LongLat(&sExtent) != OGRERR_NONE)
964 : {
965 0 : CPLError(CE_Failure, CPLE_AppDefined,
966 : "Cannot get dataset extent in WGS84 longitude/latitude");
967 0 : return false;
968 : }
969 :
970 2 : bool ret = OpenFileUpdateMode();
971 2 : if (ret)
972 : {
973 2 : m_featureGroup.reset(H5_CHECK(H5Gopen(m_hdf5, "WaterLevel")));
974 : }
975 :
976 2 : ret = ret && m_featureGroup;
977 2 : double dfNumInstances = 0;
978 2 : ret = ret && GH5_FetchAttribute(m_featureGroup, "numInstances",
979 : dfNumInstances, true);
980 2 : if (ret && !(dfNumInstances >= 1 && dfNumInstances <= 99 &&
981 2 : std::round(dfNumInstances) == dfNumInstances))
982 : {
983 0 : CPLError(CE_Failure, CPLE_AppDefined,
984 : "Invalid value for numInstances");
985 0 : ret = false;
986 : }
987 2 : else if (ret && dfNumInstances == 99)
988 : {
989 0 : CPLError(CE_Failure, CPLE_AppDefined,
990 : "Too many existing feature instances");
991 0 : ret = false;
992 : }
993 : else
994 : {
995 2 : double dfMainVerticalDatum = 0;
996 2 : ret = ret && GH5_FetchAttribute(m_hdf5, "verticalDatum",
997 : dfMainVerticalDatum, true);
998 :
999 2 : const int newNumInstances = static_cast<int>(dfNumInstances) + 1;
1000 2 : ret = ret && GH5_WriteAttribute(m_featureGroup, "numInstances",
1001 : newNumInstances);
1002 2 : ret = ret && CreateFeatureInstanceGroup(
1003 : CPLSPrintf("WaterLevel.%02d", newNumInstances));
1004 2 : ret = ret && FillFeatureInstanceGroup(oMapTimestampToDS,
1005 : pfnProgress, pProgressData);
1006 2 : if (dfMainVerticalDatum != m_nVerticalDatum)
1007 : {
1008 4 : ret = ret && WriteVerticalDatumReference(
1009 : m_featureInstanceGroup,
1010 2 : m_nVerticalDatum <= 1024 ? 1 : 2);
1011 2 : ret =
1012 4 : ret && WriteVerticalDatum(m_featureInstanceGroup,
1013 2 : H5T_STD_I32LE, m_nVerticalDatum);
1014 : }
1015 : }
1016 :
1017 2 : return Close() && ret;
1018 : }
1019 : else
1020 : {
1021 36 : bool ret = CreateFile();
1022 36 : ret = ret && WriteProductSpecification("INT.IHO.S-104.2.0");
1023 36 : ret = ret && WriteIssueDate();
1024 36 : ret = ret && WriteIssueTime(/* bAutogenerateFromCurrent = */ true);
1025 36 : ret = ret && WriteHorizontalCRS();
1026 36 : ret = ret && WriteTopLevelBoundingBox();
1027 :
1028 36 : const char *pszGeographicIdentifier = m_aosOptions.FetchNameValueDef(
1029 : "GEOGRAPHIC_IDENTIFIER",
1030 36 : m_poSrcDS->GetMetadataItem("geographicIdentifier"));
1031 36 : if (pszGeographicIdentifier)
1032 : {
1033 0 : ret =
1034 0 : ret && WriteVarLengthStringValue(m_hdf5, "geographicIdentifier",
1035 : pszGeographicIdentifier);
1036 : }
1037 :
1038 36 : const char *pszVerticalCS = m_aosOptions.FetchNameValueDef(
1039 36 : "VERTICAL_CS", m_poSrcDS->GetMetadataItem("verticalCS"));
1040 36 : if (!pszVerticalCS)
1041 : {
1042 1 : CPLError(CE_Failure, CPLE_AppDefined,
1043 : "VERTICAL_CS creation option must be specified");
1044 1 : return false;
1045 : }
1046 38 : const int nVerticalCS = EQUAL(pszVerticalCS, "DEPTH") ? 6498
1047 3 : : EQUAL(pszVerticalCS, "HEIGHT")
1048 3 : ? 6499
1049 3 : : atoi(pszVerticalCS);
1050 35 : if (nVerticalCS != 6498 && nVerticalCS != 6499)
1051 : {
1052 1 : CPLError(CE_Failure, CPLE_NotSupported,
1053 : "VERTICAL_CS creation option must be set either to 6498 "
1054 : "(depth/down, metre), or 6499 (height/up, metre)");
1055 1 : return false;
1056 : }
1057 :
1058 34 : ret = ret && WriteVerticalCS(nVerticalCS);
1059 34 : ret = ret && WriteVerticalCoordinateBase(2); // verticalDatum
1060 : // 1=s100VerticalDatum, 2=EPSG
1061 64 : ret = ret && WriteVerticalDatumReference(
1062 30 : m_hdf5, m_nVerticalDatum <= 1024 ? 1 : 2);
1063 34 : ret =
1064 34 : ret && WriteVerticalDatum(m_hdf5, H5T_STD_I32LE, m_nVerticalDatum);
1065 :
1066 : const char *pszWaterLevelTrendThreshold =
1067 34 : m_aosOptions.FetchNameValueDef(
1068 : "WATER_LEVEL_TREND_THRESHOLD",
1069 34 : m_poSrcDS->GetMetadataItem("waterLevelTrendThreshold"));
1070 34 : if (!pszWaterLevelTrendThreshold)
1071 : {
1072 1 : CPLError(CE_Failure, CPLE_AppDefined,
1073 : "WATER_LEVEL_TREND_THRESHOLD creation option must be "
1074 : "specified.");
1075 1 : return false;
1076 : }
1077 33 : if (CPLGetValueType(pszWaterLevelTrendThreshold) == CPL_VALUE_STRING)
1078 : {
1079 1 : CPLError(CE_Failure, CPLE_AppDefined,
1080 : "WATER_LEVEL_TREND_THRESHOLD creation option value must "
1081 : "be a numeric value.");
1082 1 : return false;
1083 : }
1084 32 : ret = ret && WriteFloat32Value(m_hdf5, "waterLevelTrendThreshold",
1085 : CPLAtof(pszWaterLevelTrendThreshold));
1086 :
1087 32 : const char *pszDatasetDeliveryInterval = m_aosOptions.FetchNameValueDef(
1088 : "DATASET_DELIVERY_INTERVAL",
1089 32 : m_poSrcDS->GetMetadataItem("datasetDeliveryInterval"));
1090 32 : if (pszDatasetDeliveryInterval)
1091 : {
1092 0 : ret = ret &&
1093 0 : WriteVarLengthStringValue(m_hdf5, "datasetDeliveryInterval",
1094 : pszDatasetDeliveryInterval);
1095 : }
1096 :
1097 32 : const char *pszTrendInterval = m_aosOptions.FetchNameValueDef(
1098 32 : "TREND_INTERVAL", m_poSrcDS->GetMetadataItem("trendInterval"));
1099 32 : if (pszTrendInterval)
1100 : {
1101 0 : if (CPLGetValueType(pszTrendInterval) != CPL_VALUE_INTEGER)
1102 : {
1103 0 : CPLError(CE_Failure, CPLE_AppDefined,
1104 : "TREND_INTERVAL creation option value must "
1105 : "be an integer value.");
1106 0 : return false;
1107 : }
1108 0 : ret = ret && WriteUInt32Value(m_hdf5, "trendInterval",
1109 0 : atoi(pszTrendInterval));
1110 : }
1111 :
1112 : // WaterLevel
1113 32 : ret = ret && CreateFeatureGroup(FEATURE_TYPE);
1114 32 : ret = ret && WriteFeatureGroupAttributes();
1115 32 : ret = ret && WriteAxisNames(m_featureGroup);
1116 :
1117 32 : ret = ret && CreateFeatureInstanceGroup("WaterLevel.01");
1118 32 : ret = ret && FillFeatureInstanceGroup(oMapTimestampToDS, pfnProgress,
1119 : pProgressData);
1120 :
1121 32 : ret = ret && CreateGroupF();
1122 :
1123 32 : return Close() && ret;
1124 : }
1125 : }
1126 :
1127 : /************************************************************************/
1128 : /* S104Creator::WriteFeatureGroupAttributes() */
1129 : /************************************************************************/
1130 :
1131 28 : bool S104Creator::WriteFeatureGroupAttributes()
1132 : {
1133 28 : CPLAssert(m_featureGroup);
1134 :
1135 : // 4 = all (recommended)
1136 28 : const char *pszCommonPointRule = m_aosOptions.FetchNameValueDef(
1137 28 : "COMMON_POINT_RULE", m_poSrcDS->GetMetadataItem("commonPointRule"));
1138 28 : if (!pszCommonPointRule)
1139 26 : pszCommonPointRule = "4"; // all (recommended)
1140 28 : const int nCommonPointRule = atoi(pszCommonPointRule);
1141 28 : bool ret = WriteCommonPointRule(m_featureGroup, nCommonPointRule);
1142 28 : ret = ret && WriteDataCodingFormat(m_featureGroup, 2); // Regular grid
1143 28 : ret = ret && WriteDataOffsetCode(m_featureGroup, 5); // Center of cell
1144 28 : ret = ret && WriteDimension(m_featureGroup, 2);
1145 : const char *pszHorizontalPositionUncertainty =
1146 28 : m_aosOptions.FetchNameValueDef(
1147 : "HORIZONTAL_POSITION_UNCERTAINTY",
1148 28 : m_poSrcDS->GetMetadataItem("horizontalPositionUncertainty"));
1149 28 : ret =
1150 56 : ret &&
1151 30 : WriteHorizontalPositionUncertainty(
1152 : m_featureGroup,
1153 2 : pszHorizontalPositionUncertainty &&
1154 2 : pszHorizontalPositionUncertainty[0]
1155 2 : ? static_cast<float>(CPLAtof(pszHorizontalPositionUncertainty))
1156 : : -1.0f);
1157 28 : const char *pszVerticalUncertainty = m_aosOptions.FetchNameValueDef(
1158 : "VERTICAL_UNCERTAINTY",
1159 28 : m_poSrcDS->GetMetadataItem("verticalUncertainty"));
1160 30 : ret = ret && WriteVerticalUncertainty(
1161 : m_featureGroup,
1162 2 : pszVerticalUncertainty && pszVerticalUncertainty[0]
1163 2 : ? static_cast<float>(CPLAtof(pszVerticalUncertainty))
1164 : : -1.0f);
1165 28 : const char *pszTimeUncertainty = m_aosOptions.FetchNameValueDef(
1166 28 : "TIME_UNCERTAINTY", m_poSrcDS->GetMetadataItem("timeUncertainty"));
1167 28 : if (pszTimeUncertainty)
1168 0 : WriteFloat32Value(m_featureGroup, "timeUncertainty",
1169 : CPLAtof(pszTimeUncertainty));
1170 28 : const char *pszMethodWaterLevelProduct = m_aosOptions.FetchNameValueDef(
1171 : "METHOD_WATER_LEVEL_PRODUCT",
1172 28 : m_poSrcDS->GetMetadataItem("methodWaterLevelProduct"));
1173 28 : if (pszMethodWaterLevelProduct)
1174 0 : WriteVarLengthStringValue(m_featureGroup, "methodWaterLevelProduct",
1175 : pszMethodWaterLevelProduct);
1176 28 : ret = ret && WriteInterpolationType(m_featureGroup, 1); // Nearest neighbor
1177 28 : ret = ret && WriteNumInstances(m_featureGroup, H5T_STD_U32LE, 1);
1178 56 : ret = ret && WriteSequencingRuleScanDirection(m_featureGroup,
1179 28 : m_poSRS->IsProjected()
1180 : ? "Easting, Northing"
1181 : : "Longitude, Latitude");
1182 28 : ret = ret && WriteSequencingRuleType(m_featureGroup, 1); // Linear
1183 28 : return ret;
1184 : }
1185 :
1186 : /************************************************************************/
1187 : /* S104Creator::WriteUncertaintyDataset() */
1188 : /************************************************************************/
1189 :
1190 25 : bool S104Creator::WriteUncertaintyDataset()
1191 : {
1192 25 : CPLAssert(m_featureInstanceGroup);
1193 :
1194 : GH5_HIDTypeHolder hDataType(
1195 50 : H5_CHECK(H5Tcreate(H5T_COMPOUND, sizeof(char *) + sizeof(float))));
1196 50 : GH5_HIDTypeHolder hVarLengthStringDataType(H5_CHECK(H5Tcopy(H5T_C_S1)));
1197 : bool bRet =
1198 50 : hVarLengthStringDataType &&
1199 50 : H5_CHECK(H5Tset_size(hVarLengthStringDataType, H5T_VARIABLE)) >= 0;
1200 50 : bRet = bRet && hVarLengthStringDataType &&
1201 25 : H5_CHECK(
1202 : H5Tset_strpad(hVarLengthStringDataType, H5T_STR_NULLTERM)) >= 0;
1203 25 : bRet = bRet && hDataType &&
1204 25 : H5_CHECK(H5Tinsert(hDataType, "name", 0,
1205 50 : hVarLengthStringDataType)) >= 0 &&
1206 25 : H5_CHECK(H5Tinsert(hDataType, "value", sizeof(char *),
1207 : H5T_IEEE_F32LE)) >= 0;
1208 25 : hsize_t dims[] = {1};
1209 50 : GH5_HIDSpaceHolder hDataSpace(H5_CHECK(H5Screate_simple(1, dims, nullptr)));
1210 50 : GH5_HIDDatasetHolder hDatasetID;
1211 50 : GH5_HIDParametersHolder hParams(H5_CHECK(H5Pcreate(H5P_DATASET_CREATE)));
1212 25 : bRet = bRet && hParams;
1213 25 : if (bRet)
1214 : {
1215 25 : hDatasetID.reset(
1216 : H5_CHECK(H5Dcreate(m_featureInstanceGroup, "uncertainty", hDataType,
1217 : hDataSpace, hParams)));
1218 25 : bRet = hDatasetID;
1219 : }
1220 :
1221 25 : GH5_HIDSpaceHolder hFileSpace;
1222 25 : if (bRet)
1223 : {
1224 25 : hFileSpace.reset(H5_CHECK(H5Dget_space(hDatasetID)));
1225 25 : bRet = hFileSpace;
1226 : }
1227 25 : H5OFFSET_TYPE offset[] = {0};
1228 25 : hsize_t count[1] = {1};
1229 25 : const char *pszName = "uncertainty";
1230 : GByte abyValues[sizeof(char *) + sizeof(float)];
1231 25 : memcpy(abyValues, &pszName, sizeof(char *));
1232 25 : const char *pszUncertainty = m_aosOptions.FetchNameValueDef(
1233 25 : "UNCERTAINTY", m_poSrcDS->GetMetadataItem("uncertainty"));
1234 : float fVal =
1235 25 : pszUncertainty ? static_cast<float>(CPLAtof(pszUncertainty)) : -1.0f;
1236 25 : CPL_LSBPTR32(&fVal);
1237 25 : memcpy(abyValues + sizeof(char *), &fVal, sizeof(fVal));
1238 50 : bRet = bRet &&
1239 25 : H5_CHECK(H5Sselect_hyperslab(hFileSpace, H5S_SELECT_SET, offset,
1240 50 : nullptr, count, nullptr)) >= 0 &&
1241 25 : H5_CHECK(H5Dwrite(hDatasetID, hDataType, hDataSpace, hFileSpace,
1242 : H5P_DEFAULT, abyValues)) >= 0;
1243 50 : return bRet;
1244 : }
1245 :
1246 : /************************************************************************/
1247 : /* S104Creator::FillFeatureInstanceGroup() */
1248 : /************************************************************************/
1249 :
1250 30 : bool S104Creator::FillFeatureInstanceGroup(
1251 : const std::map<std::string, std::variant<GDALDataset *, std::string>>
1252 : &oMapTimestampToDS,
1253 : GDALProgressFunc pfnProgress, void *pProgressData)
1254 : {
1255 30 : bool ret = WriteFIGGridRelatedParameters(m_featureInstanceGroup);
1256 :
1257 30 : const int numInstances = static_cast<int>(oMapTimestampToDS.size());
1258 :
1259 30 : ret =
1260 30 : ret && WriteNumGRP(m_featureInstanceGroup, H5T_STD_U32LE, numInstances);
1261 30 : ret = ret && WriteUInt32Value(m_featureInstanceGroup, "numberOfTimes",
1262 : numInstances);
1263 :
1264 : // Check if value groups are spaced at a regular time interval
1265 30 : GIntBig nLastInterval = 0;
1266 30 : GIntBig nLastTS = 0;
1267 64 : for (const auto &[key, value] : oMapTimestampToDS)
1268 : {
1269 34 : CPL_IGNORE_RET_VAL(value);
1270 : int nYear, nMonth, nDay, nHour, nMinute, nSecond;
1271 34 : if (sscanf(key.c_str(), "%04d%02d%02dT%02d%02d%02dZ", &nYear, &nMonth,
1272 34 : &nDay, &nHour, &nMinute, &nSecond) == 6)
1273 : {
1274 : struct tm brokenDown;
1275 34 : memset(&brokenDown, 0, sizeof(brokenDown));
1276 34 : brokenDown.tm_year = nYear - 1900;
1277 34 : brokenDown.tm_mon = nMonth - 1;
1278 34 : brokenDown.tm_mday = nDay;
1279 34 : brokenDown.tm_hour = nHour;
1280 34 : brokenDown.tm_min = nMinute;
1281 34 : brokenDown.tm_sec = nMinute;
1282 34 : const GIntBig nTS = CPLYMDHMSToUnixTime(&brokenDown);
1283 34 : if (nLastTS != 0)
1284 : {
1285 4 : if (nLastInterval == 0)
1286 : {
1287 2 : nLastInterval = nTS - nLastTS;
1288 : }
1289 2 : else if (nLastInterval != nTS - nLastTS)
1290 : {
1291 0 : nLastInterval = 0;
1292 0 : break;
1293 : }
1294 : }
1295 34 : nLastTS = nTS;
1296 : }
1297 : }
1298 :
1299 30 : const char *pszTimeRecordInterval = m_aosOptions.FetchNameValueDef(
1300 : "TIME_RECORD_INTERVAL",
1301 30 : m_poSrcDS->GetMetadataItem("timeRecordInterval"));
1302 30 : if (pszTimeRecordInterval)
1303 : {
1304 2 : ret = ret &&
1305 1 : WriteUInt16Value(m_featureInstanceGroup, "timeRecordInterval",
1306 : atoi(pszTimeRecordInterval));
1307 : }
1308 29 : else if (nLastInterval > 0 && nLastInterval < 65536)
1309 : {
1310 2 : ret = ret &&
1311 1 : WriteUInt16Value(m_featureInstanceGroup, "timeRecordInterval",
1312 : static_cast<int>(nLastInterval));
1313 : }
1314 :
1315 60 : ret = ret && WriteVarLengthStringValue(
1316 : m_featureInstanceGroup, "dateTimeOfFirstRecord",
1317 30 : oMapTimestampToDS.begin()->first.c_str());
1318 60 : ret = ret && WriteVarLengthStringValue(
1319 : m_featureInstanceGroup, "dateTimeOfLastRecord",
1320 30 : oMapTimestampToDS.rbegin()->first.c_str());
1321 :
1322 30 : const char *pszDataDynamicity = m_aosOptions.FetchNameValueDef(
1323 30 : "DATA_DYNAMICITY", m_poSrcDS->GetMetadataItem("dataDynamicity"));
1324 30 : if (!pszDataDynamicity)
1325 : {
1326 1 : CPLError(CE_Failure, CPLE_AppDefined,
1327 : "DATA_DYNAMICITY creation option must "
1328 : "be specified.");
1329 1 : return false;
1330 : }
1331 : {
1332 : GH5_HIDTypeHolder hDataDynamicityEnumDataType(
1333 29 : H5_CHECK(H5Tenum_create(H5T_STD_U8LE)));
1334 29 : ret = ret && hDataDynamicityEnumDataType;
1335 :
1336 : uint8_t val;
1337 29 : val = 1;
1338 29 : ret = ret && H5_CHECK(H5Tenum_insert(hDataDynamicityEnumDataType,
1339 : "observation", &val)) >= 0;
1340 29 : val = 2;
1341 58 : ret = ret &&
1342 29 : H5_CHECK(H5Tenum_insert(hDataDynamicityEnumDataType,
1343 : "astronomicalPrediction", &val)) >= 0;
1344 29 : val = 3;
1345 29 : ret = ret && H5_CHECK(H5Tenum_insert(hDataDynamicityEnumDataType,
1346 : "analysisOrHybrid", &val)) >= 0;
1347 29 : val = 5;
1348 29 : ret =
1349 29 : ret && H5_CHECK(H5Tenum_insert(hDataDynamicityEnumDataType,
1350 : "hydrodynamicForecast", &val)) >= 0;
1351 :
1352 29 : const int nDataDynamicity =
1353 58 : EQUAL(pszDataDynamicity, "observation") ? 1
1354 58 : : EQUAL(pszDataDynamicity, "astronomicalPrediction") ? 2
1355 58 : : EQUAL(pszDataDynamicity, "analysisOrHybrid") ? 3
1356 29 : : EQUAL(pszDataDynamicity, "hydrodynamicForecast")
1357 29 : ? 5
1358 29 : : atoi(pszDataDynamicity);
1359 29 : if (nDataDynamicity != 1 && nDataDynamicity != 2 &&
1360 29 : nDataDynamicity != 3 && nDataDynamicity != 5)
1361 : {
1362 1 : CPLError(CE_Failure, CPLE_AppDefined,
1363 : "DATA_DYNAMICITY creation option must "
1364 : "be set to observation/1, astronomicalPrediction/2, "
1365 : "analysisOrHybrid/3 or hydrodynamicForecast/5.");
1366 1 : return false;
1367 : }
1368 28 : ret = ret &&
1369 28 : GH5_CreateAttribute(m_featureInstanceGroup, "dataDynamicity",
1370 56 : hDataDynamicityEnumDataType) &&
1371 28 : GH5_WriteAttribute(m_featureInstanceGroup, "dataDynamicity",
1372 : nDataDynamicity);
1373 : }
1374 :
1375 31 : if (m_poSrcDS->GetRasterCount() == 2 ||
1376 3 : m_aosOptions.FetchNameValue("UNCERTAINTY"))
1377 : {
1378 25 : ret = ret && WriteUncertaintyDataset();
1379 : }
1380 :
1381 28 : int iInstance = 0;
1382 28 : double dfLastRatio = 0;
1383 60 : for (const auto &iter : oMapTimestampToDS)
1384 : {
1385 32 : ++iInstance;
1386 32 : ret = ret && CreateValuesGroup(CPLSPrintf("Group_%03d", iInstance));
1387 :
1388 32 : ret = ret && WriteVarLengthStringValue(m_valuesGroup, "timePoint",
1389 : iter.first.c_str());
1390 :
1391 0 : std::unique_ptr<GDALDataset> poTmpDSHolder;
1392 : GDALDataset *poSrcDS;
1393 32 : if (std::holds_alternative<std::string>(iter.second))
1394 : {
1395 6 : poTmpDSHolder.reset(
1396 6 : GDALDataset::Open(std::get<std::string>(iter.second).c_str(),
1397 : GDAL_OF_RASTER | GDAL_OF_VERBOSE_ERROR));
1398 6 : if (!poTmpDSHolder)
1399 : {
1400 0 : return false;
1401 : }
1402 6 : poSrcDS = poTmpDSHolder.get();
1403 : }
1404 : else
1405 : {
1406 26 : CPLAssert(std::holds_alternative<GDALDataset *>(iter.second));
1407 26 : poSrcDS = std::get<GDALDataset *>(iter.second);
1408 : }
1409 :
1410 32 : const double dfNewRatio = static_cast<double>(iInstance) / numInstances;
1411 : std::unique_ptr<void, decltype(&GDALDestroyScaledProgress)>
1412 : pScaledProgressData(
1413 : GDALCreateScaledProgress(dfLastRatio, dfNewRatio, pfnProgress,
1414 : pProgressData),
1415 32 : GDALDestroyScaledProgress);
1416 32 : ret = ret && CopyValues(poSrcDS, GDALScaledProgress,
1417 : pScaledProgressData.get());
1418 32 : dfLastRatio = dfNewRatio;
1419 : }
1420 :
1421 28 : return ret;
1422 : }
1423 :
1424 : /************************************************************************/
1425 : /* S104Creator::CreateGroupF() */
1426 : /************************************************************************/
1427 :
1428 : // Per S-104 v2.0 spec
1429 : #define MIN_WATER_LEVEL_HEIGHT_VALUE -99.99
1430 : #define MAX_WATER_LEVEL_HEIGHT_VALUE 99.99
1431 :
1432 : #define STRINGIFY(x) #x
1433 : #define XSTRINGIFY(x) STRINGIFY(x)
1434 :
1435 26 : bool S104Creator::CreateGroupF()
1436 : {
1437 26 : bool ret = S100BaseWriter::CreateGroupF();
1438 :
1439 26 : CPLStringList aosFeatureCodes;
1440 26 : aosFeatureCodes.push_back(FEATURE_TYPE);
1441 52 : ret = ret && WriteOneDimensionalVarLengthStringArray(
1442 26 : m_GroupF, "featureCode", aosFeatureCodes.List());
1443 :
1444 : {
1445 : std::vector<std::array<const char *, GROUP_F_DATASET_FIELD_COUNT>> rows{
1446 : {"waterLevelHeight", "Water Level Height", "metre", "-9999.00",
1447 : "H5T_FLOAT", XSTRINGIFY(MIN_WATER_LEVEL_HEIGHT_VALUE),
1448 : XSTRINGIFY(MAX_WATER_LEVEL_HEIGHT_VALUE), "closedInterval"},
1449 : {"waterLevelTrend", "Water Level Trend", "", "0", "H5T_ENUM", "",
1450 : "", ""},
1451 : {"uncertainty", "Uncertainty", "metre", "-1.00", "H5T_FLOAT",
1452 26 : "0.00", "99.99", "closedInterval"}};
1453 26 : rows.resize(m_poSrcDS->GetRasterCount());
1454 26 : ret = ret && WriteGroupFDataset(FEATURE_TYPE, rows);
1455 : }
1456 :
1457 52 : return ret;
1458 : }
1459 :
1460 : /************************************************************************/
1461 : /* S104Creator::CopyValues() */
1462 : /************************************************************************/
1463 :
1464 32 : bool S104Creator::CopyValues(GDALDataset *poSrcDS, GDALProgressFunc pfnProgress,
1465 : void *pProgressData)
1466 : {
1467 32 : CPLAssert(m_valuesGroup.get() >= 0);
1468 :
1469 32 : const int nYSize = poSrcDS->GetRasterYSize();
1470 32 : const int nXSize = poSrcDS->GetRasterXSize();
1471 :
1472 32 : hsize_t dims[] = {static_cast<hsize_t>(nYSize),
1473 32 : static_cast<hsize_t>(nXSize)};
1474 :
1475 64 : GH5_HIDSpaceHolder hDataSpace(H5_CHECK(H5Screate_simple(2, dims, nullptr)));
1476 32 : bool bRet = hDataSpace;
1477 :
1478 : const bool bDeflate =
1479 32 : EQUAL(m_aosOptions.FetchNameValueDef("COMPRESS", "DEFLATE"), "DEFLATE");
1480 : const int nCompressionLevel =
1481 32 : atoi(m_aosOptions.FetchNameValueDef("ZLEVEL", "6"));
1482 : const int nBlockSize =
1483 32 : std::min(4096, std::max(100, atoi(m_aosOptions.FetchNameValueDef(
1484 32 : "BLOCK_SIZE", "100"))));
1485 32 : const int nBlockXSize = std::min(nXSize, nBlockSize);
1486 32 : const int nBlockYSize = std::min(nYSize, nBlockSize);
1487 32 : constexpr float fNoDataValueHeight = -9999.0f;
1488 32 : constexpr GByte nNoDataValueTrend = 0;
1489 32 : constexpr float fNoDataValueUncertainty = -1.0f;
1490 32 : const int nComponents = poSrcDS->GetRasterCount();
1491 :
1492 : GH5_HIDTypeHolder hTrendEnumDataType(
1493 64 : H5_CHECK(H5Tenum_create(H5T_STD_U8LE)));
1494 32 : bRet = bRet && hTrendEnumDataType;
1495 : {
1496 : uint8_t val;
1497 32 : val = 1;
1498 32 : bRet = bRet && H5_CHECK(H5Tenum_insert(hTrendEnumDataType, "Decreasing",
1499 : &val)) >= 0;
1500 32 : val = 2;
1501 32 : bRet = bRet && H5_CHECK(H5Tenum_insert(hTrendEnumDataType, "Increasing",
1502 : &val)) >= 0;
1503 32 : val = 3;
1504 32 : bRet = bRet && H5_CHECK(H5Tenum_insert(hTrendEnumDataType, "Steady",
1505 : &val)) >= 0;
1506 : }
1507 :
1508 : GH5_HIDTypeHolder hDataType(H5_CHECK(
1509 : H5Tcreate(H5T_COMPOUND, sizeof(float) + sizeof(GByte) +
1510 64 : (nComponents == 3 ? sizeof(float) : 0))));
1511 32 : bRet = bRet && hDataType &&
1512 32 : H5_CHECK(H5Tinsert(hDataType, "waterLevelHeight", 0,
1513 64 : H5T_IEEE_F32LE)) >= 0 &&
1514 32 : H5_CHECK(H5Tinsert(hDataType, "waterLevelTrend", sizeof(float),
1515 : hTrendEnumDataType)) >= 0;
1516 32 : if (nComponents == 3 && bRet)
1517 : {
1518 3 : bRet = H5_CHECK(H5Tinsert(hDataType, "uncertainty",
1519 : sizeof(float) + sizeof(GByte),
1520 : H5T_IEEE_F32LE)) >= 0;
1521 : }
1522 :
1523 32 : hsize_t chunk_size[] = {static_cast<hsize_t>(nBlockYSize),
1524 32 : static_cast<hsize_t>(nBlockXSize)};
1525 :
1526 64 : GH5_HIDParametersHolder hParams(H5_CHECK(H5Pcreate(H5P_DATASET_CREATE)));
1527 32 : bRet = bRet && hParams &&
1528 32 : H5_CHECK(H5Pset_fill_time(hParams, H5D_FILL_TIME_ALLOC)) >= 0 &&
1529 96 : H5_CHECK(H5Pset_layout(hParams, H5D_CHUNKED)) >= 0 &&
1530 32 : H5_CHECK(H5Pset_chunk(hParams, 2, chunk_size)) >= 0;
1531 :
1532 32 : if (bRet && bDeflate)
1533 : {
1534 31 : bRet = H5_CHECK(H5Pset_deflate(hParams, nCompressionLevel)) >= 0;
1535 : }
1536 :
1537 64 : GH5_HIDDatasetHolder hDatasetID;
1538 32 : if (bRet)
1539 : {
1540 32 : hDatasetID.reset(H5_CHECK(H5Dcreate(m_valuesGroup, "values", hDataType,
1541 : hDataSpace, hParams)));
1542 32 : bRet = hDatasetID;
1543 : }
1544 :
1545 64 : GH5_HIDSpaceHolder hFileSpace;
1546 32 : if (bRet)
1547 : {
1548 32 : hFileSpace.reset(H5_CHECK(H5Dget_space(hDatasetID)));
1549 32 : bRet = hFileSpace;
1550 : }
1551 :
1552 32 : const int nYBlocks = static_cast<int>(DIV_ROUND_UP(nYSize, nBlockYSize));
1553 32 : const int nXBlocks = static_cast<int>(DIV_ROUND_UP(nXSize, nBlockXSize));
1554 32 : std::vector<float> afValues(static_cast<size_t>(nBlockYSize) * nBlockXSize *
1555 64 : nComponents);
1556 : std::vector<GByte> abyValues(
1557 32 : static_cast<size_t>(nBlockYSize) * nBlockXSize *
1558 32 : (sizeof(float) + sizeof(GByte) + sizeof(float)));
1559 32 : const bool bReverseY = m_gt[5] < 0;
1560 :
1561 32 : float fMinHeight = std::numeric_limits<float>::infinity();
1562 32 : float fMaxHeight = -std::numeric_limits<float>::infinity();
1563 32 : float fMinTrend = std::numeric_limits<float>::infinity();
1564 32 : float fMaxTrend = -std::numeric_limits<float>::infinity();
1565 32 : float fMinUncertainty = std::numeric_limits<float>::infinity();
1566 32 : float fMaxUncertainty = -std::numeric_limits<float>::infinity();
1567 :
1568 32 : int bHasNoDataBand1 = FALSE;
1569 : const double dfSrcNoDataBand1 =
1570 32 : poSrcDS->GetRasterBand(1)->GetNoDataValue(&bHasNoDataBand1);
1571 32 : const float fSrcNoDataBand1 = static_cast<float>(dfSrcNoDataBand1);
1572 :
1573 32 : int bHasNoDataBand3 = FALSE;
1574 : const double dfSrcNoDataBand3 =
1575 : nComponents == 3
1576 32 : ? poSrcDS->GetRasterBand(3)->GetNoDataValue(&bHasNoDataBand3)
1577 32 : : 0.0;
1578 32 : const float fSrcNoDataBand3 = static_cast<float>(dfSrcNoDataBand3);
1579 :
1580 75 : for (int iY = 0; iY < nYBlocks && bRet; iY++)
1581 : {
1582 : const int nSrcYOff = bReverseY
1583 43 : ? std::max(0, nYSize - (iY + 1) * nBlockYSize)
1584 38 : : iY * nBlockYSize;
1585 43 : const int nReqCountY = std::min(nBlockYSize, nYSize - iY * nBlockYSize);
1586 218 : for (int iX = 0; iX < nXBlocks && bRet; iX++)
1587 : {
1588 : const int nReqCountX =
1589 175 : std::min(nBlockXSize, nXSize - iX * nBlockXSize);
1590 :
1591 175 : bRet =
1592 175 : poSrcDS->RasterIO(
1593 175 : GF_Read, iX * nBlockXSize, nSrcYOff, nReqCountX, nReqCountY,
1594 5 : bReverseY ? afValues.data() +
1595 5 : (nReqCountY - 1) * nReqCountX * nComponents
1596 170 : : afValues.data(),
1597 : nReqCountX, nReqCountY, GDT_Float32, nComponents, nullptr,
1598 175 : static_cast<int>(sizeof(float)) * nComponents,
1599 : bReverseY ? -static_cast<GPtrDiff_t>(sizeof(float)) *
1600 5 : nComponents * nReqCountX
1601 : : 0,
1602 : sizeof(float), nullptr) == CE_None;
1603 :
1604 175 : if (bRet)
1605 : {
1606 175 : size_t nOffset = 0;
1607 1440440 : for (int i = 0; i < nReqCountY * nReqCountX; i++)
1608 : {
1609 : {
1610 1440260 : float fVal = afValues[i * nComponents];
1611 2880520 : if ((bHasNoDataBand1 && fVal == fSrcNoDataBand1) ||
1612 1440260 : std::isnan(fVal))
1613 : {
1614 1 : fVal = fNoDataValueHeight;
1615 : }
1616 : else
1617 : {
1618 1440260 : fMinHeight = std::min(fMinHeight, fVal);
1619 1440260 : fMaxHeight = std::max(fMaxHeight, fVal);
1620 : }
1621 1440260 : CPL_LSBPTR32(&fVal);
1622 1440260 : memcpy(abyValues.data() + nOffset, &fVal, sizeof(fVal));
1623 1440260 : nOffset += sizeof(fVal);
1624 : }
1625 : {
1626 1440260 : const float fVal = afValues[i * nComponents + 1];
1627 1440260 : if (fVal != nNoDataValueTrend)
1628 : {
1629 203 : fMinTrend = std::min(fMinTrend, fVal);
1630 203 : fMaxTrend = std::max(fMaxTrend, fVal);
1631 : }
1632 1440260 : abyValues[nOffset] = static_cast<GByte>(fVal);
1633 1440260 : nOffset += sizeof(GByte);
1634 : }
1635 1440260 : if (nComponents == 3)
1636 : {
1637 1440020 : float fVal = afValues[i * nComponents + 2];
1638 2880040 : if ((bHasNoDataBand3 && fVal == fSrcNoDataBand3) ||
1639 1440020 : std::isnan(fVal))
1640 : {
1641 1 : fVal = fNoDataValueUncertainty;
1642 : }
1643 : else
1644 : {
1645 1440020 : fMinUncertainty = std::min(fMinUncertainty, fVal);
1646 1440020 : fMaxUncertainty = std::max(fMaxUncertainty, fVal);
1647 : }
1648 1440020 : CPL_LSBPTR32(&fVal);
1649 1440020 : memcpy(abyValues.data() + nOffset, &fVal, sizeof(fVal));
1650 1440020 : nOffset += sizeof(fVal);
1651 : }
1652 : }
1653 : }
1654 :
1655 : H5OFFSET_TYPE offset[] = {
1656 175 : static_cast<H5OFFSET_TYPE>(iY) *
1657 175 : static_cast<H5OFFSET_TYPE>(nBlockYSize),
1658 175 : static_cast<H5OFFSET_TYPE>(iX) *
1659 175 : static_cast<H5OFFSET_TYPE>(nBlockXSize)};
1660 175 : hsize_t count[2] = {static_cast<hsize_t>(nReqCountY),
1661 175 : static_cast<hsize_t>(nReqCountX)};
1662 : GH5_HIDSpaceHolder hMemSpace(
1663 175 : H5_CHECK(H5Screate_simple(2, count, nullptr)));
1664 175 : bRet =
1665 175 : bRet &&
1666 175 : H5_CHECK(H5Sselect_hyperslab(hFileSpace, H5S_SELECT_SET, offset,
1667 175 : nullptr, count, nullptr)) >= 0 &&
1668 175 : hMemSpace &&
1669 175 : H5_CHECK(H5Dwrite(hDatasetID, hDataType, hMemSpace, hFileSpace,
1670 350 : H5P_DEFAULT, abyValues.data())) >= 0 &&
1671 175 : pfnProgress((static_cast<double>(iY) * nXBlocks + iX + 1) /
1672 175 : (static_cast<double>(nXBlocks) * nYBlocks),
1673 : "", pProgressData) != 0;
1674 : }
1675 : }
1676 :
1677 32 : if (fMinHeight > fMaxHeight)
1678 : {
1679 0 : fMinHeight = fMaxHeight = fNoDataValueHeight;
1680 : }
1681 32 : else if (!(fMinHeight >= MIN_WATER_LEVEL_HEIGHT_VALUE &&
1682 31 : fMaxHeight <= MAX_WATER_LEVEL_HEIGHT_VALUE))
1683 : {
1684 2 : CPLError(CE_Warning, CPLE_AppDefined,
1685 : "Range of water level height in the dataset is [%f, %f] "
1686 : "whereas the "
1687 : "allowed range is [%.2f, %.2f]",
1688 : fMinHeight, fMaxHeight, MIN_WATER_LEVEL_HEIGHT_VALUE,
1689 : MAX_WATER_LEVEL_HEIGHT_VALUE);
1690 : }
1691 :
1692 32 : if (fMaxTrend >= fMinTrend && fMinTrend < 1)
1693 : {
1694 0 : CPLError(
1695 : CE_Warning, CPLE_AppDefined,
1696 : "Negative water level trend value found, which is not allowed");
1697 : }
1698 32 : if (fMaxTrend >= fMinTrend && fMaxTrend > 3)
1699 : {
1700 0 : CPLError(CE_Warning, CPLE_AppDefined,
1701 : "Water level trend value > 3 found, which is not allowed");
1702 : }
1703 :
1704 32 : if (fMaxUncertainty >= fMinUncertainty && fMinUncertainty < 0)
1705 : {
1706 1 : CPLError(CE_Warning, CPLE_AppDefined,
1707 : "Negative uncertainty value found (%f), which is not allowed "
1708 : "(except nodata value -1.0)",
1709 : fMinUncertainty);
1710 : }
1711 :
1712 32 : if (bRet)
1713 : {
1714 32 : double prevMinHeight = 0;
1715 32 : double prevMaxHeight = 0;
1716 32 : if (GH5_FetchAttribute(m_featureGroup, "minDatasetHeight",
1717 38 : prevMinHeight) &&
1718 6 : GH5_FetchAttribute(m_featureGroup, "maxDatasetHeight",
1719 : prevMaxHeight))
1720 : {
1721 6 : if (fMinHeight != fNoDataValueHeight)
1722 : {
1723 6 : prevMinHeight = std::min<double>(prevMinHeight, fMinHeight);
1724 6 : prevMaxHeight = std::max<double>(prevMaxHeight, fMaxHeight);
1725 12 : bRet = GH5_WriteAttribute(m_featureGroup, "minDatasetHeight",
1726 12 : prevMinHeight) &&
1727 6 : GH5_WriteAttribute(m_featureGroup, "maxDatasetHeight",
1728 : prevMaxHeight);
1729 : }
1730 : }
1731 : else
1732 : {
1733 52 : bRet = WriteFloat32Value(m_featureGroup, "minDatasetHeight",
1734 52 : fMinHeight) &&
1735 26 : WriteFloat32Value(m_featureGroup, "maxDatasetHeight",
1736 : fMaxHeight);
1737 : }
1738 : }
1739 :
1740 64 : return bRet;
1741 : }
1742 :
1743 : /************************************************************************/
1744 : /* S104DatasetDriverUnload() */
1745 : /************************************************************************/
1746 :
1747 9 : static void S104DatasetDriverUnload(GDALDriver *)
1748 : {
1749 9 : HDF5UnloadFileDriver();
1750 9 : }
1751 :
1752 : /************************************************************************/
1753 : /* S104Dataset::CreateCopy() */
1754 : /************************************************************************/
1755 :
1756 : /* static */
1757 72 : GDALDataset *S104Dataset::CreateCopy(const char *pszFilename,
1758 : GDALDataset *poSrcDS, int /* bStrict*/,
1759 : char **papszOptions,
1760 : GDALProgressFunc pfnProgress,
1761 : void *pProgressData)
1762 : {
1763 144 : S104Creator creator(pszFilename, poSrcDS, papszOptions);
1764 72 : if (!creator.Create(pfnProgress, pProgressData))
1765 45 : return nullptr;
1766 :
1767 : VSIStatBufL sStatBuf;
1768 54 : if (VSIStatL(pszFilename, &sStatBuf) == 0 &&
1769 27 : sStatBuf.st_size > 10 * 1024 * 1024)
1770 : {
1771 1 : CPLError(CE_Warning, CPLE_AppDefined,
1772 : "%s file size exceeds 10 MB, which is the upper limit "
1773 : "suggested for wireless transmission to marine vessels",
1774 : pszFilename);
1775 : }
1776 :
1777 54 : GDALOpenInfo oOpenInfo(pszFilename, GA_ReadOnly);
1778 27 : return Open(&oOpenInfo);
1779 : }
1780 :
1781 : /************************************************************************/
1782 : /* GDALRegister_S104() */
1783 : /************************************************************************/
1784 14 : void GDALRegister_S104()
1785 :
1786 : {
1787 14 : if (!GDAL_CHECK_VERSION("S104"))
1788 0 : return;
1789 :
1790 14 : if (GDALGetDriverByName(S104_DRIVER_NAME) != nullptr)
1791 0 : return;
1792 :
1793 14 : GDALDriver *poDriver = new GDALDriver();
1794 :
1795 14 : S104DriverSetCommonMetadata(poDriver);
1796 14 : poDriver->pfnOpen = S104Dataset::Open;
1797 14 : poDriver->pfnCreateCopy = S104Dataset::CreateCopy;
1798 14 : poDriver->pfnUnloadDriver = S104DatasetDriverUnload;
1799 :
1800 14 : GetGDALDriverManager()->RegisterDriver(poDriver);
1801 : }
|