Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: Hierarchical Data Format Release 5 (HDF5)
4 : * Purpose: Read S102 bathymetric datasets.
5 : * Author: Even Rouault <even dot rouault at spatialys dot com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2023, Even Rouault <even dot rouault at spatialys dot com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_port.h"
14 : #include "hdf5dataset.h"
15 : #include "hdf5drivercore.h"
16 : #include "gh5_convenience.h"
17 : #include "rat.h"
18 : #include "s100.h"
19 :
20 : #include "gdal_frmts.h"
21 : #include "gdal_priv.h"
22 : #include "gdal_proxy.h"
23 : #include "gdal_rat.h"
24 :
25 : #include <cmath>
26 : #include <limits>
27 :
28 : /************************************************************************/
29 : /* S102Dataset */
30 : /************************************************************************/
31 :
32 54 : class S102Dataset final : public S100BaseDataset
33 : {
34 : bool OpenQuality(GDALOpenInfo *poOpenInfo,
35 : const std::shared_ptr<GDALGroup> &poRootGroup);
36 :
37 : public:
38 27 : explicit S102Dataset(const std::string &osFilename)
39 27 : : S100BaseDataset(osFilename)
40 : {
41 27 : }
42 :
43 : ~S102Dataset() override;
44 :
45 : static GDALDataset *Open(GDALOpenInfo *);
46 : };
47 :
48 : S102Dataset::~S102Dataset() = default;
49 :
50 : /************************************************************************/
51 : /* S102RasterBand */
52 : /************************************************************************/
53 :
54 : class S102RasterBand final : public GDALProxyRasterBand
55 : {
56 : friend class S102Dataset;
57 : std::unique_ptr<GDALDataset> m_poDS{};
58 : GDALRasterBand *m_poUnderlyingBand = nullptr;
59 : double m_dfMinimum = std::numeric_limits<double>::quiet_NaN();
60 : double m_dfMaximum = std::numeric_limits<double>::quiet_NaN();
61 :
62 : CPL_DISALLOW_COPY_ASSIGN(S102RasterBand)
63 :
64 : public:
65 33 : explicit S102RasterBand(std::unique_ptr<GDALDataset> &&poDSIn)
66 66 : : m_poDS(std::move(poDSIn)),
67 33 : m_poUnderlyingBand(m_poDS->GetRasterBand(1))
68 : {
69 33 : eDataType = m_poUnderlyingBand->GetRasterDataType();
70 33 : m_poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
71 33 : }
72 :
73 : GDALRasterBand *
74 : RefUnderlyingRasterBand(bool /*bForceOpen*/ = true) const override;
75 :
76 8 : double GetMinimum(int *pbSuccess = nullptr) override
77 : {
78 8 : if (pbSuccess)
79 8 : *pbSuccess = !std::isnan(m_dfMinimum);
80 8 : return m_dfMinimum;
81 : }
82 :
83 8 : double GetMaximum(int *pbSuccess = nullptr) override
84 : {
85 8 : if (pbSuccess)
86 8 : *pbSuccess = !std::isnan(m_dfMaximum);
87 8 : return m_dfMaximum;
88 : }
89 :
90 4 : const char *GetUnitType() override
91 : {
92 4 : return "metre";
93 : }
94 : };
95 :
96 : GDALRasterBand *
97 25 : S102RasterBand::RefUnderlyingRasterBand(bool /*bForceOpen*/) const
98 : {
99 25 : return m_poUnderlyingBand;
100 : }
101 :
102 : /************************************************************************/
103 : /* S102GeoreferencedMetadataRasterBand */
104 : /************************************************************************/
105 :
106 : class S102GeoreferencedMetadataRasterBand final : public GDALProxyRasterBand
107 : {
108 : friend class S102Dataset;
109 :
110 : std::unique_ptr<GDALDataset> m_poDS{};
111 : GDALRasterBand *m_poUnderlyingBand = nullptr;
112 : std::unique_ptr<GDALRasterAttributeTable> m_poRAT{};
113 :
114 : CPL_DISALLOW_COPY_ASSIGN(S102GeoreferencedMetadataRasterBand)
115 :
116 : public:
117 5 : explicit S102GeoreferencedMetadataRasterBand(
118 : std::unique_ptr<GDALDataset> &&poDSIn,
119 : std::unique_ptr<GDALRasterAttributeTable> &&poRAT)
120 10 : : m_poDS(std::move(poDSIn)),
121 5 : m_poUnderlyingBand(m_poDS->GetRasterBand(1)),
122 10 : m_poRAT(std::move(poRAT))
123 : {
124 5 : eDataType = m_poUnderlyingBand->GetRasterDataType();
125 5 : m_poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
126 5 : }
127 :
128 : GDALRasterBand *
129 : RefUnderlyingRasterBand(bool /*bForceOpen*/ = true) const override;
130 :
131 2 : GDALRasterAttributeTable *GetDefaultRAT() override
132 : {
133 2 : return m_poRAT.get();
134 : }
135 : };
136 :
137 7 : GDALRasterBand *S102GeoreferencedMetadataRasterBand::RefUnderlyingRasterBand(
138 : bool /*bForceOpen*/) const
139 : {
140 7 : return m_poUnderlyingBand;
141 : }
142 :
143 : /************************************************************************/
144 : /* Open() */
145 : /************************************************************************/
146 :
147 31 : GDALDataset *S102Dataset::Open(GDALOpenInfo *poOpenInfo)
148 :
149 : {
150 : // Confirm that this appears to be a S102 file.
151 31 : if (!S102DatasetIdentify(poOpenInfo))
152 0 : return nullptr;
153 :
154 : HDF5_GLOBAL_LOCK();
155 :
156 31 : if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
157 : {
158 2 : return HDF5Dataset::OpenMultiDim(poOpenInfo);
159 : }
160 :
161 : // Confirm the requested access is supported.
162 29 : if (poOpenInfo->eAccess == GA_Update)
163 : {
164 0 : ReportUpdateNotSupportedByDriver("S102");
165 0 : return nullptr;
166 : }
167 :
168 58 : std::string osFilename(poOpenInfo->pszFilename);
169 29 : bool bIsSubdataset = false;
170 29 : bool bIsQuality = false;
171 58 : std::string osBathymetryCoverageName = "BathymetryCoverage.01";
172 29 : if (STARTS_WITH(poOpenInfo->pszFilename, "S102:"))
173 : {
174 : const CPLStringList aosTokens(
175 15 : CSLTokenizeString2(poOpenInfo->pszFilename, ":",
176 15 : CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES));
177 :
178 15 : if (aosTokens.size() == 2)
179 : {
180 1 : osFilename = aosTokens[1];
181 : }
182 14 : else if (aosTokens.size() == 3)
183 : {
184 14 : bIsSubdataset = true;
185 14 : osFilename = aosTokens[1];
186 14 : if (EQUAL(aosTokens[2], "BathymetryCoverage"))
187 : {
188 : // Default dataset
189 : }
190 12 : else if (STARTS_WITH(aosTokens[2], "BathymetryCoverage"))
191 : {
192 3 : osBathymetryCoverageName = aosTokens[2];
193 : }
194 14 : else if (EQUAL(aosTokens[2], "QualityOfSurvey") || // < v3
195 5 : EQUAL(aosTokens[2], "QualityOfBathymetryCoverage")) // v3
196 : {
197 7 : bIsQuality = true;
198 : }
199 : else
200 : {
201 2 : CPLError(CE_Failure, CPLE_NotSupported,
202 : "Unsupported subdataset component: '%s'. Expected "
203 : "'QualityOfSurvey'",
204 : aosTokens[2]);
205 2 : return nullptr;
206 : }
207 : }
208 : else
209 : {
210 0 : return nullptr;
211 : }
212 : }
213 :
214 54 : auto poDS = std::make_unique<S102Dataset>(osFilename);
215 27 : if (!poDS->Init())
216 0 : return nullptr;
217 :
218 27 : const auto &poRootGroup = poDS->m_poRootGroup;
219 :
220 81 : auto poBathymetryCoverage = poRootGroup->OpenGroup("BathymetryCoverage");
221 27 : if (!poBathymetryCoverage)
222 : {
223 0 : CPLError(CE_Failure, CPLE_AppDefined,
224 : "S102: Cannot find /BathymetryCoverage group");
225 0 : return nullptr;
226 : }
227 :
228 27 : if (!bIsSubdataset)
229 : {
230 : auto poNumInstances =
231 30 : poBathymetryCoverage->GetAttribute("numInstances");
232 16 : if (poNumInstances &&
233 16 : poNumInstances->GetDataType().GetClass() == GEDTC_NUMERIC)
234 : {
235 1 : const int nNumInstances = poNumInstances->ReadAsInt();
236 1 : if (nNumInstances != 1)
237 : {
238 2 : CPLStringList aosSubDSList;
239 1 : int iSubDS = 0;
240 2 : for (const std::string &coverageName :
241 5 : poBathymetryCoverage->GetGroupNames())
242 : {
243 : auto poCoverage =
244 4 : poBathymetryCoverage->OpenGroup(coverageName);
245 2 : if (poCoverage)
246 : {
247 4 : GDALMajorObject mo;
248 : // Read first vertical datum from root group and let the
249 : // coverage override it.
250 2 : S100ReadVerticalDatum(&mo, poRootGroup.get());
251 2 : S100ReadVerticalDatum(&mo, poCoverage.get());
252 2 : ++iSubDS;
253 : aosSubDSList.SetNameValue(
254 : CPLSPrintf("SUBDATASET_%d_NAME", iSubDS),
255 : CPLSPrintf("S102:\"%s\":%s", osFilename.c_str(),
256 2 : coverageName.c_str()));
257 4 : std::string verticalDatum;
258 : const char *pszValue =
259 2 : mo.GetMetadataItem(S100_VERTICAL_DATUM_MEANING);
260 2 : if (pszValue)
261 : {
262 2 : verticalDatum = ", vertical datum ";
263 2 : verticalDatum += pszValue;
264 : pszValue =
265 2 : mo.GetMetadataItem(S100_VERTICAL_DATUM_ABBREV);
266 2 : if (pszValue)
267 : {
268 2 : verticalDatum += " (";
269 2 : verticalDatum += pszValue;
270 2 : verticalDatum += ')';
271 : }
272 : }
273 : else
274 : {
275 : pszValue =
276 0 : mo.GetMetadataItem(S100_VERTICAL_DATUM_NAME);
277 0 : if (pszValue)
278 : {
279 0 : verticalDatum = ", vertical datum ";
280 0 : verticalDatum += pszValue;
281 : }
282 : }
283 : aosSubDSList.SetNameValue(
284 : CPLSPrintf("SUBDATASET_%d_DESC", iSubDS),
285 : CPLSPrintf(
286 : "Bathymetric gridded data, instance %s%s",
287 2 : coverageName.c_str(), verticalDatum.c_str()));
288 : }
289 : }
290 : auto poGroupQuality =
291 3 : poRootGroup->OpenGroup("QualityOfBathymetryCoverage");
292 1 : if (poGroupQuality)
293 : {
294 : auto poQualityOfBathymetryCoverage01 =
295 1 : poGroupQuality->OpenGroup(
296 3 : "QualityOfBathymetryCoverage.01");
297 1 : if (poQualityOfBathymetryCoverage01)
298 : {
299 1 : ++iSubDS;
300 : aosSubDSList.SetNameValue(
301 : CPLSPrintf("SUBDATASET_%d_NAME", iSubDS),
302 : CPLSPrintf(
303 : "S102:\"%s\":QualityOfBathymetryCoverage",
304 1 : osFilename.c_str()));
305 : aosSubDSList.SetNameValue(
306 : CPLSPrintf("SUBDATASET_%d_DESC", iSubDS),
307 : "Georeferenced metadata "
308 1 : "QualityOfBathymetryCoverage");
309 : }
310 : }
311 :
312 1 : poDS->GDALDataset::SetMetadata(aosSubDSList.List(),
313 : "SUBDATASETS");
314 :
315 : // Setup/check for pam .aux.xml.
316 1 : poDS->SetDescription(osFilename.c_str());
317 1 : poDS->TryLoadXML();
318 :
319 : // Setup overviews.
320 1 : poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
321 :
322 1 : return poDS.release();
323 : }
324 : }
325 : }
326 :
327 26 : if (bIsQuality)
328 : {
329 7 : if (!poDS->OpenQuality(poOpenInfo, poRootGroup))
330 2 : return nullptr;
331 :
332 : // Setup/check for pam .aux.xml.
333 5 : poDS->SetDescription(osFilename.c_str());
334 5 : poDS->TryLoadXML();
335 :
336 : // Setup overviews.
337 5 : poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
338 :
339 5 : return poDS.release();
340 : }
341 :
342 : auto poBathymetryCoverageInstance =
343 38 : poBathymetryCoverage->OpenGroup(osBathymetryCoverageName);
344 19 : if (!poBathymetryCoverageInstance)
345 : {
346 1 : CPLError(CE_Failure, CPLE_AppDefined,
347 : "S102: Cannot find %s group in BathymetryCoverage group",
348 : osBathymetryCoverageName.c_str());
349 1 : return nullptr;
350 : }
351 :
352 18 : if (auto poStartSequence =
353 36 : poBathymetryCoverage->GetAttribute("startSequence"))
354 : {
355 0 : const char *pszStartSequence = poStartSequence->ReadAsString();
356 0 : if (pszStartSequence && !EQUAL(pszStartSequence, "0,0"))
357 : {
358 : // Shouldn't happen given this is imposed by the spec.
359 : // Cf 4.2.1.1.1.12 "startSequence" of Ed 3.0 spec, page 13
360 0 : CPLError(CE_Failure, CPLE_AppDefined,
361 : "startSequence (=%s) != 0,0 is not supported",
362 : pszStartSequence);
363 0 : return nullptr;
364 : }
365 : }
366 :
367 : // Potentially override vertical datum
368 18 : S100ReadVerticalDatum(poDS.get(), poBathymetryCoverageInstance.get());
369 :
370 18 : const bool bNorthUp = CPLTestBool(
371 18 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "NORTH_UP", "YES"));
372 :
373 : // Compute geotransform
374 18 : poDS->m_bHasGT = S100GetGeoTransform(poBathymetryCoverageInstance.get(),
375 18 : poDS->m_gt, bNorthUp);
376 :
377 54 : auto poGroup001 = poBathymetryCoverageInstance->OpenGroup("Group_001");
378 18 : if (!poGroup001)
379 : {
380 0 : CPLError(CE_Failure, CPLE_AppDefined,
381 : "S102: Cannot find "
382 : "/BathymetryCoverage/BathymetryCoverage.01/Group_001");
383 0 : return nullptr;
384 : }
385 54 : auto poValuesArray = poGroup001->OpenMDArray("values");
386 18 : if (!poValuesArray || poValuesArray->GetDimensionCount() != 2)
387 : {
388 0 : CPLError(CE_Failure, CPLE_AppDefined,
389 : "S102: Cannot find "
390 : "/BathymetryCoverage/BathymetryCoverage.01/Group_001/values");
391 0 : return nullptr;
392 : }
393 18 : const auto &oType = poValuesArray->GetDataType();
394 18 : if (oType.GetClass() != GEDTC_COMPOUND)
395 : {
396 0 : CPLError(CE_Failure, CPLE_AppDefined,
397 : "S102: Wrong type for "
398 : "/BathymetryCoverage/BathymetryCoverage.01/Group_001/values");
399 0 : return nullptr;
400 : }
401 18 : const auto &oComponents = oType.GetComponents();
402 18 : if (oComponents.size() == 0 || oComponents[0]->GetName() != "depth")
403 : {
404 0 : CPLError(CE_Failure, CPLE_AppDefined,
405 : "S102: Wrong type for "
406 : "/BathymetryCoverage/BathymetryCoverage.01/Group_001/values");
407 0 : return nullptr;
408 : }
409 :
410 18 : if (bNorthUp)
411 17 : poValuesArray = poValuesArray->GetView("[::-1,...]");
412 :
413 54 : auto poDepth = poValuesArray->GetView("[\"depth\"]");
414 :
415 : // Mandatory in v2.2. Since v3.0, EPSG:6498 is the only allowed value
416 18 : bool bCSIsElevation = false;
417 54 : auto poVerticalCS = poRootGroup->GetAttribute("verticalCS");
418 18 : if (poVerticalCS && poVerticalCS->GetDataType().GetClass() == GEDTC_NUMERIC)
419 : {
420 8 : const auto nVal = poVerticalCS->ReadAsInt();
421 8 : if (nVal == 6498) // Depth metre
422 : {
423 : // nothing to do
424 : }
425 0 : else if (nVal == 6499) // Height metre
426 : {
427 0 : bCSIsElevation = true;
428 : }
429 : else
430 : {
431 0 : CPLError(CE_Warning, CPLE_NotSupported, "Unsupported verticalCS=%d",
432 : nVal);
433 : }
434 : }
435 :
436 : const bool bUseElevation =
437 18 : EQUAL(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
438 : "DEPTH_OR_ELEVATION", "DEPTH"),
439 : "ELEVATION");
440 35 : const bool bInvertDepth = (bUseElevation && !bCSIsElevation) ||
441 17 : (!bUseElevation && bCSIsElevation);
442 18 : const double dfDepthNoData = poDepth->GetNoDataValueAsDouble();
443 37 : auto poDepthDS = [&poDepth, bInvertDepth, dfDepthNoData]()
444 : {
445 18 : if (bInvertDepth)
446 : {
447 1 : auto poInverted = poDepth->GetUnscaled(-1, 0, dfDepthNoData);
448 : return std::unique_ptr<GDALDataset>(
449 1 : poInverted->AsClassicDataset(1, 0));
450 : }
451 : else
452 : {
453 : return std::unique_ptr<GDALDataset>(
454 17 : poDepth->AsClassicDataset(1, 0));
455 : }
456 36 : }();
457 :
458 18 : poDS->nRasterXSize = poDepthDS->GetRasterXSize();
459 18 : poDS->nRasterYSize = poDepthDS->GetRasterYSize();
460 :
461 : // Create depth (or elevation) band
462 18 : auto poDepthBand = new S102RasterBand(std::move(poDepthDS));
463 18 : poDepthBand->SetDescription(bUseElevation ? "elevation" : "depth");
464 :
465 54 : auto poMinimumDepth = poGroup001->GetAttribute("minimumDepth");
466 36 : if (poMinimumDepth &&
467 36 : poMinimumDepth->GetDataType().GetClass() == GEDTC_NUMERIC)
468 : {
469 18 : const double dfVal = poMinimumDepth->ReadAsDouble();
470 18 : if (dfVal != dfDepthNoData)
471 : {
472 14 : if (bInvertDepth)
473 1 : poDepthBand->m_dfMaximum = -dfVal;
474 : else
475 13 : poDepthBand->m_dfMinimum = dfVal;
476 : }
477 : }
478 :
479 54 : auto poMaximumDepth = poGroup001->GetAttribute("maximumDepth");
480 36 : if (poMaximumDepth &&
481 36 : poMaximumDepth->GetDataType().GetClass() == GEDTC_NUMERIC)
482 : {
483 18 : const double dfVal = poMaximumDepth->ReadAsDouble();
484 18 : if (dfVal != dfDepthNoData)
485 : {
486 18 : if (bInvertDepth)
487 1 : poDepthBand->m_dfMinimum = -dfVal;
488 : else
489 17 : poDepthBand->m_dfMaximum = dfVal;
490 : }
491 : }
492 :
493 18 : poDS->SetBand(1, poDepthBand);
494 :
495 : const bool bHasUncertainty =
496 18 : oComponents.size() >= 2 && oComponents[1]->GetName() == "uncertainty";
497 18 : if (bHasUncertainty)
498 : {
499 : // Create uncertainty band
500 45 : auto poUncertainty = poValuesArray->GetView("[\"uncertainty\"]");
501 : const double dfUncertaintyNoData =
502 15 : poUncertainty->GetNoDataValueAsDouble();
503 : auto poUncertaintyDS =
504 30 : std::unique_ptr<GDALDataset>(poUncertainty->AsClassicDataset(1, 0));
505 :
506 15 : auto poUncertaintyBand = new S102RasterBand(std::move(poUncertaintyDS));
507 15 : poUncertaintyBand->SetDescription("uncertainty");
508 :
509 : auto poMinimumUncertainty =
510 45 : poGroup001->GetAttribute("minimumUncertainty");
511 30 : if (poMinimumUncertainty &&
512 30 : poMinimumUncertainty->GetDataType().GetClass() == GEDTC_NUMERIC)
513 : {
514 15 : const double dfVal = poMinimumUncertainty->ReadAsDouble();
515 15 : if (dfVal != dfUncertaintyNoData)
516 : {
517 15 : poUncertaintyBand->m_dfMinimum = dfVal;
518 : }
519 : }
520 :
521 : auto poMaximumUncertainty =
522 45 : poGroup001->GetAttribute("maximumUncertainty");
523 30 : if (poMaximumUncertainty &&
524 30 : poMaximumUncertainty->GetDataType().GetClass() == GEDTC_NUMERIC)
525 : {
526 15 : const double dfVal = poMaximumUncertainty->ReadAsDouble();
527 15 : if (dfVal != dfUncertaintyNoData)
528 : {
529 15 : poUncertaintyBand->m_dfMaximum = dfVal;
530 : }
531 : }
532 :
533 15 : poDS->SetBand(2, poUncertaintyBand);
534 : }
535 :
536 18 : poDS->GDALDataset::SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT);
537 :
538 54 : auto poGroupQuality = poRootGroup->OpenGroup("QualityOfSurvey");
539 18 : const bool bIsNamedQualityOfSurvey = poGroupQuality != nullptr;
540 18 : if (!bIsNamedQualityOfSurvey)
541 : {
542 : // S102 v3 now uses QualityOfBathymetryCoverage instead of QualityOfSurvey
543 16 : poGroupQuality = poRootGroup->OpenGroup("QualityOfBathymetryCoverage");
544 : }
545 18 : if (!bIsSubdataset && poGroupQuality)
546 : {
547 3 : const char *pszNameOfQualityGroup = bIsNamedQualityOfSurvey
548 3 : ? "QualityOfSurvey"
549 : : "QualityOfBathymetryCoverage";
550 3 : auto poGroupQuality01 = poGroupQuality->OpenGroup(
551 9 : CPLSPrintf("%s.01", pszNameOfQualityGroup));
552 3 : if (poGroupQuality01)
553 : {
554 3 : poDS->GDALDataset::SetMetadataItem(
555 : "SUBDATASET_1_NAME",
556 : CPLSPrintf("S102:\"%s\":BathymetryCoverage",
557 : osFilename.c_str()),
558 : "SUBDATASETS");
559 3 : poDS->GDALDataset::SetMetadataItem(
560 : "SUBDATASET_1_DESC", "Bathymetric gridded data", "SUBDATASETS");
561 :
562 3 : poDS->GDALDataset::SetMetadataItem(
563 : "SUBDATASET_2_NAME",
564 : CPLSPrintf("S102:\"%s\":%s", osFilename.c_str(),
565 : pszNameOfQualityGroup),
566 : "SUBDATASETS");
567 3 : poDS->GDALDataset::SetMetadataItem(
568 : "SUBDATASET_2_DESC",
569 : CPLSPrintf("Georeferenced metadata %s", pszNameOfQualityGroup),
570 : "SUBDATASETS");
571 : }
572 : }
573 :
574 : // Setup/check for pam .aux.xml.
575 18 : poDS->SetDescription(osFilename.c_str());
576 18 : poDS->TryLoadXML();
577 :
578 : // Setup overviews.
579 18 : poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
580 :
581 18 : return poDS.release();
582 : }
583 :
584 : /************************************************************************/
585 : /* OpenQuality() */
586 : /************************************************************************/
587 :
588 7 : bool S102Dataset::OpenQuality(GDALOpenInfo *poOpenInfo,
589 : const std::shared_ptr<GDALGroup> &poRootGroup)
590 : {
591 7 : const bool bNorthUp = CPLTestBool(
592 7 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "NORTH_UP", "YES"));
593 :
594 7 : const char *pszNameOfQualityGroup = "QualityOfSurvey";
595 21 : auto poGroupQuality = poRootGroup->OpenGroup(pszNameOfQualityGroup);
596 7 : if (!poGroupQuality)
597 : {
598 5 : pszNameOfQualityGroup = "QualityOfBathymetryCoverage";
599 5 : poGroupQuality = poRootGroup->OpenGroup(pszNameOfQualityGroup);
600 5 : if (!poGroupQuality)
601 : {
602 2 : CPLError(CE_Failure, CPLE_AppDefined,
603 : "Cannot find group /QualityOfSurvey or "
604 : "/QualityOfBathymetryCoverage");
605 2 : return false;
606 : }
607 : }
608 :
609 : const std::string osQuality01Name =
610 15 : std::string(pszNameOfQualityGroup).append(".01");
611 10 : const std::string osQuality01FullName = std::string("/")
612 5 : .append(pszNameOfQualityGroup)
613 5 : .append("/")
614 10 : .append(osQuality01Name);
615 10 : auto poGroupQuality01 = poGroupQuality->OpenGroup(osQuality01Name);
616 5 : if (!poGroupQuality01)
617 : {
618 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find group %s",
619 : osQuality01FullName.c_str());
620 0 : return false;
621 : }
622 :
623 10 : if (auto poStartSequence = poGroupQuality01->GetAttribute("startSequence"))
624 : {
625 1 : const char *pszStartSequence = poStartSequence->ReadAsString();
626 1 : if (pszStartSequence && !EQUAL(pszStartSequence, "0,0"))
627 : {
628 0 : CPLError(CE_Failure, CPLE_AppDefined,
629 : "startSequence (=%s) != 0,0 is not supported",
630 : pszStartSequence);
631 0 : return false;
632 : }
633 : }
634 :
635 : // Compute geotransform
636 5 : m_bHasGT = S100GetGeoTransform(poGroupQuality01.get(), m_gt, bNorthUp);
637 :
638 15 : auto poGroup001 = poGroupQuality01->OpenGroup("Group_001");
639 5 : if (!poGroup001)
640 : {
641 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot find group %s/Group_001",
642 : osQuality01FullName.c_str());
643 0 : return false;
644 : }
645 :
646 15 : auto poValuesArray = poGroup001->OpenMDArray("values");
647 5 : if (!poValuesArray)
648 : {
649 0 : CPLError(CE_Failure, CPLE_AppDefined,
650 : "Cannot find array "
651 : "%s/Group_001/values",
652 : osQuality01FullName.c_str());
653 0 : return false;
654 : }
655 :
656 : {
657 5 : const auto &oType = poValuesArray->GetDataType();
658 8 : if (oType.GetClass() == GEDTC_NUMERIC &&
659 3 : oType.GetNumericDataType() == GDT_UInt32)
660 : {
661 : // ok
662 : }
663 4 : else if (oType.GetClass() == GEDTC_COMPOUND &&
664 4 : oType.GetComponents().size() == 1 &&
665 2 : oType.GetComponents()[0]->GetType().GetClass() ==
666 4 : GEDTC_NUMERIC &&
667 2 : oType.GetComponents()[0]->GetType().GetNumericDataType() ==
668 : GDT_UInt32)
669 : {
670 : // seen in a S102 v3 product (102DE00CA22_UNC_MD.H5), although
671 : // I believe this is non-conformant.
672 :
673 : // Escape potentials single-quote and double-quote with back-slash
674 2 : CPLString osEscapedCompName(oType.GetComponents()[0]->GetName());
675 4 : osEscapedCompName.replaceAll("\\", "\\\\")
676 4 : .replaceAll("'", "\\'")
677 2 : .replaceAll("\"", "\\\"");
678 :
679 : // Gets a view with that single component extracted.
680 4 : poValuesArray = poValuesArray->GetView(
681 4 : std::string("['").append(osEscapedCompName).append("']"));
682 2 : if (!poValuesArray)
683 0 : return false;
684 : }
685 : else
686 : {
687 0 : CPLError(CE_Failure, CPLE_NotSupported,
688 : "Unsupported data type for %s",
689 0 : poValuesArray->GetFullName().c_str());
690 0 : return false;
691 : }
692 : }
693 :
694 5 : if (poValuesArray->GetDimensionCount() != 2)
695 : {
696 0 : CPLError(CE_Failure, CPLE_NotSupported,
697 : "Unsupported number of dimensions for %s",
698 0 : poValuesArray->GetFullName().c_str());
699 0 : return false;
700 : }
701 :
702 : auto poFeatureAttributeTable =
703 15 : poGroupQuality->OpenMDArray("featureAttributeTable");
704 5 : if (!poFeatureAttributeTable)
705 : {
706 0 : CPLError(CE_Failure, CPLE_AppDefined,
707 : "Cannot find array /%s/featureAttributeTable",
708 : pszNameOfQualityGroup);
709 0 : return false;
710 : }
711 :
712 : {
713 5 : const auto &oType = poFeatureAttributeTable->GetDataType();
714 5 : if (oType.GetClass() != GEDTC_COMPOUND)
715 : {
716 0 : CPLError(CE_Failure, CPLE_NotSupported,
717 : "Unsupported data type for %s",
718 0 : poFeatureAttributeTable->GetFullName().c_str());
719 0 : return false;
720 : }
721 :
722 5 : const auto &poComponents = oType.GetComponents();
723 5 : if (poComponents.size() >= 1 && poComponents[0]->GetName() != "id")
724 : {
725 0 : CPLError(CE_Failure, CPLE_AppDefined,
726 : "Missing 'id' component in %s",
727 0 : poFeatureAttributeTable->GetFullName().c_str());
728 0 : return false;
729 : }
730 : }
731 :
732 5 : if (bNorthUp)
733 3 : poValuesArray = poValuesArray->GetView("[::-1,...]");
734 :
735 : auto poDS =
736 10 : std::unique_ptr<GDALDataset>(poValuesArray->AsClassicDataset(1, 0));
737 5 : if (!poDS)
738 0 : return false;
739 :
740 5 : nRasterXSize = poDS->GetRasterXSize();
741 5 : nRasterYSize = poDS->GetRasterYSize();
742 :
743 : auto poRAT =
744 10 : HDF5CreateRAT(poFeatureAttributeTable, /* bFirstColIsMinMax = */ true);
745 : auto poBand = std::make_unique<S102GeoreferencedMetadataRasterBand>(
746 5 : std::move(poDS), std::move(poRAT));
747 5 : SetBand(1, poBand.release());
748 :
749 5 : return true;
750 : }
751 :
752 : /************************************************************************/
753 : /* S102DatasetDriverUnload() */
754 : /************************************************************************/
755 :
756 323 : static void S102DatasetDriverUnload(GDALDriver *)
757 : {
758 323 : HDF5UnloadFileDriver();
759 323 : }
760 :
761 : /************************************************************************/
762 : /* GDALRegister_S102() */
763 : /************************************************************************/
764 355 : void GDALRegister_S102()
765 :
766 : {
767 355 : if (!GDAL_CHECK_VERSION("S102"))
768 0 : return;
769 :
770 355 : if (GDALGetDriverByName(S102_DRIVER_NAME) != nullptr)
771 0 : return;
772 :
773 355 : GDALDriver *poDriver = new GDALDriver();
774 :
775 355 : S102DriverSetCommonMetadata(poDriver);
776 355 : poDriver->pfnOpen = S102Dataset::Open;
777 355 : poDriver->pfnUnloadDriver = S102DatasetDriverUnload;
778 :
779 355 : GetGDALDriverManager()->RegisterDriver(poDriver);
780 : }
|