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