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 : * Permission is hereby granted, free of charge, to any person obtaining a
11 : * copy of this software and associated documentation files (the "Software"),
12 : * to deal in the Software without restriction, including without limitation
13 : * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14 : * and/or sell copies of the Software, and to permit persons to whom the
15 : * Software is furnished to do so, subject to the following conditions:
16 : *
17 : * The above copyright notice and this permission notice shall be included
18 : * in all copies or substantial portions of the Software.
19 : *
20 : * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
21 : * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 : * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
23 : * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24 : * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25 : * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 : * DEALINGS IN THE SOFTWARE.
27 : ****************************************************************************/
28 :
29 : #include "cpl_port.h"
30 : #include "hdf5dataset.h"
31 : #include "hdf5drivercore.h"
32 : #include "gh5_convenience.h"
33 : #include "rat.h"
34 : #include "s100.h"
35 :
36 : #include "gdal_priv.h"
37 : #include "gdal_proxy.h"
38 : #include "gdal_rat.h"
39 :
40 : #include <limits>
41 :
42 : /************************************************************************/
43 : /* S102Dataset */
44 : /************************************************************************/
45 :
46 : class S102Dataset final : public S100BaseDataset
47 : {
48 : bool OpenQualityOfSurvey(GDALOpenInfo *poOpenInfo,
49 : const std::shared_ptr<GDALGroup> &poRootGroup);
50 :
51 : public:
52 11 : explicit S102Dataset(const std::string &osFilename)
53 11 : : S100BaseDataset(osFilename)
54 : {
55 11 : }
56 :
57 : static GDALDataset *Open(GDALOpenInfo *);
58 : };
59 :
60 : /************************************************************************/
61 : /* S102RasterBand */
62 : /************************************************************************/
63 :
64 : class S102RasterBand : public GDALProxyRasterBand
65 : {
66 : friend class S102Dataset;
67 : std::unique_ptr<GDALDataset> m_poDS{};
68 : GDALRasterBand *m_poUnderlyingBand = nullptr;
69 : double m_dfMinimum = std::numeric_limits<double>::quiet_NaN();
70 : double m_dfMaximum = std::numeric_limits<double>::quiet_NaN();
71 :
72 : public:
73 14 : explicit S102RasterBand(std::unique_ptr<GDALDataset> &&poDSIn)
74 28 : : m_poDS(std::move(poDSIn)),
75 14 : m_poUnderlyingBand(m_poDS->GetRasterBand(1))
76 : {
77 14 : eDataType = m_poUnderlyingBand->GetRasterDataType();
78 14 : m_poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
79 14 : }
80 :
81 : GDALRasterBand *
82 16 : RefUnderlyingRasterBand(bool /*bForceOpen*/ = true) const override
83 : {
84 16 : return m_poUnderlyingBand;
85 : }
86 :
87 8 : double GetMinimum(int *pbSuccess = nullptr) override
88 : {
89 8 : if (pbSuccess)
90 8 : *pbSuccess = !std::isnan(m_dfMinimum);
91 8 : return m_dfMinimum;
92 : }
93 :
94 8 : double GetMaximum(int *pbSuccess = nullptr) override
95 : {
96 8 : if (pbSuccess)
97 8 : *pbSuccess = !std::isnan(m_dfMaximum);
98 8 : return m_dfMaximum;
99 : }
100 :
101 4 : const char *GetUnitType() override
102 : {
103 4 : return "metre";
104 : }
105 : };
106 :
107 : /************************************************************************/
108 : /* S102GeoreferencedMetadataRasterBand */
109 : /************************************************************************/
110 :
111 : class S102GeoreferencedMetadataRasterBand : public GDALProxyRasterBand
112 : {
113 : friend class S102Dataset;
114 :
115 : std::unique_ptr<GDALDataset> m_poDS{};
116 : GDALRasterBand *m_poUnderlyingBand = nullptr;
117 : std::unique_ptr<GDALRasterAttributeTable> m_poRAT{};
118 :
119 : public:
120 2 : explicit S102GeoreferencedMetadataRasterBand(
121 : std::unique_ptr<GDALDataset> &&poDSIn,
122 : std::unique_ptr<GDALRasterAttributeTable> &&poRAT)
123 4 : : m_poDS(std::move(poDSIn)),
124 2 : m_poUnderlyingBand(m_poDS->GetRasterBand(1)),
125 4 : m_poRAT(std::move(poRAT))
126 : {
127 2 : eDataType = m_poUnderlyingBand->GetRasterDataType();
128 2 : m_poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
129 2 : }
130 :
131 : GDALRasterBand *
132 3 : RefUnderlyingRasterBand(bool /*bForceOpen*/ = true) const override
133 : {
134 3 : return m_poUnderlyingBand;
135 : }
136 :
137 1 : GDALRasterAttributeTable *GetDefaultRAT() override
138 : {
139 1 : return m_poRAT.get();
140 : }
141 : };
142 :
143 : /************************************************************************/
144 : /* Open() */
145 : /************************************************************************/
146 :
147 14 : GDALDataset *S102Dataset::Open(GDALOpenInfo *poOpenInfo)
148 :
149 : {
150 : // Confirm that this appears to be a S102 file.
151 14 : if (!S102DatasetIdentify(poOpenInfo))
152 0 : return nullptr;
153 :
154 : HDF5_GLOBAL_LOCK();
155 :
156 14 : if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
157 : {
158 2 : return HDF5Dataset::OpenMultiDim(poOpenInfo);
159 : }
160 :
161 : // Confirm the requested access is supported.
162 12 : if (poOpenInfo->eAccess == GA_Update)
163 : {
164 0 : CPLError(CE_Failure, CPLE_NotSupported,
165 : "The S102 driver does not support update access.");
166 0 : return nullptr;
167 : }
168 :
169 24 : std::string osFilename(poOpenInfo->pszFilename);
170 12 : bool bIsSubdataset = false;
171 12 : bool bIsQualityOfSurvey = false;
172 12 : if (STARTS_WITH(poOpenInfo->pszFilename, "S102:"))
173 : {
174 : const CPLStringList aosTokens(
175 7 : CSLTokenizeString2(poOpenInfo->pszFilename, ":",
176 7 : CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES));
177 :
178 7 : if (aosTokens.size() == 2)
179 : {
180 1 : osFilename = aosTokens[1];
181 : }
182 6 : else if (aosTokens.size() == 3)
183 : {
184 6 : bIsSubdataset = true;
185 6 : osFilename = aosTokens[1];
186 6 : if (EQUAL(aosTokens[2], "BathymetryCoverage"))
187 : {
188 : // Default dataset
189 : }
190 5 : else if (EQUAL(aosTokens[2], "QualityOfSurvey"))
191 : {
192 4 : bIsQualityOfSurvey = true;
193 : }
194 : else
195 : {
196 1 : CPLError(CE_Failure, CPLE_NotSupported,
197 : "Unsupported subdataset component: '%s'. Expected "
198 : "'QualityOfSurvey'",
199 : aosTokens[2]);
200 1 : return nullptr;
201 : }
202 : }
203 : else
204 : {
205 0 : return nullptr;
206 : }
207 : }
208 :
209 22 : auto poDS = std::make_unique<S102Dataset>(osFilename);
210 11 : if (!poDS->Init())
211 0 : return nullptr;
212 :
213 11 : const auto &poRootGroup = poDS->m_poRootGroup;
214 : auto poBathymetryCoverage01 = poRootGroup->OpenGroupFromFullname(
215 33 : "/BathymetryCoverage/BathymetryCoverage.01");
216 11 : if (!poBathymetryCoverage01)
217 0 : return nullptr;
218 :
219 11 : const bool bNorthUp = CPLTestBool(
220 11 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "NORTH_UP", "YES"));
221 :
222 11 : if (bIsQualityOfSurvey)
223 : {
224 4 : if (!poDS->OpenQualityOfSurvey(poOpenInfo, poRootGroup))
225 2 : return nullptr;
226 :
227 : // Setup/check for pam .aux.xml.
228 2 : poDS->SetDescription(osFilename.c_str());
229 2 : poDS->TryLoadXML();
230 :
231 : // Setup overviews.
232 2 : poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
233 :
234 2 : return poDS.release();
235 : }
236 :
237 : // Compute geotransform
238 7 : poDS->m_bHasGT = S100GetGeoTransform(poBathymetryCoverage01.get(),
239 7 : poDS->m_adfGeoTransform, bNorthUp);
240 :
241 21 : auto poGroup001 = poBathymetryCoverage01->OpenGroup("Group_001");
242 7 : if (!poGroup001)
243 0 : return nullptr;
244 21 : auto poValuesArray = poGroup001->OpenMDArray("values");
245 7 : if (!poValuesArray || poValuesArray->GetDimensionCount() != 2)
246 0 : return nullptr;
247 :
248 7 : const auto &oType = poValuesArray->GetDataType();
249 7 : if (oType.GetClass() != GEDTC_COMPOUND)
250 0 : return nullptr;
251 7 : const auto &oComponents = oType.GetComponents();
252 14 : if (oComponents.size() != 2 || oComponents[0]->GetName() != "depth" ||
253 7 : oComponents[1]->GetName() != "uncertainty")
254 : {
255 0 : return nullptr;
256 : }
257 :
258 7 : if (bNorthUp)
259 6 : poValuesArray = poValuesArray->GetView("[::-1,...]");
260 :
261 21 : auto poDepth = poValuesArray->GetView("[\"depth\"]");
262 :
263 : // Mandatory in v2.2
264 7 : bool bCSIsElevation = false;
265 21 : auto poVerticalCS = poRootGroup->GetAttribute("verticalCS");
266 7 : if (poVerticalCS && poVerticalCS->GetDataType().GetClass() == GEDTC_NUMERIC)
267 : {
268 3 : const auto nVal = poVerticalCS->ReadAsInt();
269 3 : if (nVal == 6498) // Depth metre
270 : {
271 : // nothing to do
272 : }
273 0 : else if (nVal == 6499) // Height metre
274 : {
275 0 : bCSIsElevation = true;
276 : }
277 : else
278 : {
279 0 : CPLError(CE_Warning, CPLE_NotSupported, "Unsupported verticalCS=%d",
280 : nVal);
281 : }
282 : }
283 :
284 : const bool bUseElevation =
285 7 : EQUAL(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
286 : "DEPTH_OR_ELEVATION", "DEPTH"),
287 : "ELEVATION");
288 13 : const bool bInvertDepth = (bUseElevation && !bCSIsElevation) ||
289 6 : (!bUseElevation && bCSIsElevation);
290 7 : const double dfDepthNoData = poDepth->GetNoDataValueAsDouble();
291 15 : auto poDepthDS = [&poDepth, bInvertDepth, dfDepthNoData]()
292 : {
293 7 : if (bInvertDepth)
294 : {
295 1 : auto poInverted = poDepth->GetUnscaled(-1, 0, dfDepthNoData);
296 : return std::unique_ptr<GDALDataset>(
297 1 : poInverted->AsClassicDataset(1, 0));
298 : }
299 : else
300 : {
301 : return std::unique_ptr<GDALDataset>(
302 6 : poDepth->AsClassicDataset(1, 0));
303 : }
304 14 : }();
305 :
306 21 : auto poUncertainty = poValuesArray->GetView("[\"uncertainty\"]");
307 7 : const double dfUncertaintyNoData = poUncertainty->GetNoDataValueAsDouble();
308 : auto poUncertaintyDS =
309 14 : std::unique_ptr<GDALDataset>(poUncertainty->AsClassicDataset(1, 0));
310 :
311 7 : poDS->nRasterXSize = poDepthDS->GetRasterXSize();
312 7 : poDS->nRasterYSize = poDepthDS->GetRasterYSize();
313 :
314 : // Create depth (or elevation) band
315 7 : auto poDepthBand = new S102RasterBand(std::move(poDepthDS));
316 7 : poDepthBand->SetDescription(bUseElevation ? "elevation" : "depth");
317 :
318 21 : auto poMinimumDepth = poGroup001->GetAttribute("minimumDepth");
319 14 : if (poMinimumDepth &&
320 14 : poMinimumDepth->GetDataType().GetClass() == GEDTC_NUMERIC)
321 : {
322 7 : const double dfVal = poMinimumDepth->ReadAsDouble();
323 7 : if (dfVal != dfDepthNoData)
324 : {
325 7 : if (bInvertDepth)
326 1 : poDepthBand->m_dfMaximum = -dfVal;
327 : else
328 6 : poDepthBand->m_dfMinimum = dfVal;
329 : }
330 : }
331 :
332 21 : auto poMaximumDepth = poGroup001->GetAttribute("maximumDepth");
333 14 : if (poMaximumDepth &&
334 14 : poMaximumDepth->GetDataType().GetClass() == GEDTC_NUMERIC)
335 : {
336 7 : const double dfVal = poMaximumDepth->ReadAsDouble();
337 7 : if (dfVal != dfDepthNoData)
338 : {
339 7 : if (bInvertDepth)
340 1 : poDepthBand->m_dfMinimum = -dfVal;
341 : else
342 6 : poDepthBand->m_dfMaximum = dfVal;
343 : }
344 : }
345 :
346 7 : poDS->SetBand(1, poDepthBand);
347 :
348 : // Create uncertainty band
349 7 : auto poUncertaintyBand = new S102RasterBand(std::move(poUncertaintyDS));
350 7 : poUncertaintyBand->SetDescription("uncertainty");
351 :
352 21 : auto poMinimumUncertainty = poGroup001->GetAttribute("minimumUncertainty");
353 14 : if (poMinimumUncertainty &&
354 14 : poMinimumUncertainty->GetDataType().GetClass() == GEDTC_NUMERIC)
355 : {
356 7 : const double dfVal = poMinimumUncertainty->ReadAsDouble();
357 7 : if (dfVal != dfUncertaintyNoData)
358 : {
359 7 : poUncertaintyBand->m_dfMinimum = dfVal;
360 : }
361 : }
362 :
363 21 : auto poMaximumUncertainty = poGroup001->GetAttribute("maximumUncertainty");
364 14 : if (poMaximumUncertainty &&
365 14 : poMaximumUncertainty->GetDataType().GetClass() == GEDTC_NUMERIC)
366 : {
367 7 : const double dfVal = poMaximumUncertainty->ReadAsDouble();
368 7 : if (dfVal != dfUncertaintyNoData)
369 : {
370 7 : poUncertaintyBand->m_dfMaximum = dfVal;
371 : }
372 : }
373 :
374 7 : poDS->SetBand(2, poUncertaintyBand);
375 :
376 7 : poDS->GDALDataset::SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT);
377 :
378 21 : auto poGroupQualityOfSurvey = poRootGroup->OpenGroup("QualityOfSurvey");
379 7 : if (!bIsSubdataset && poGroupQualityOfSurvey)
380 : {
381 : auto poGroupQualityOfSurvey01 =
382 3 : poGroupQualityOfSurvey->OpenGroup("QualityOfSurvey.01");
383 1 : if (poGroupQualityOfSurvey01)
384 : {
385 1 : poDS->GDALDataset::SetMetadataItem(
386 : "SUBDATASET_1_NAME",
387 : CPLSPrintf("S102:\"%s\":BathymetryCoverage",
388 : osFilename.c_str()),
389 : "SUBDATASETS");
390 1 : poDS->GDALDataset::SetMetadataItem(
391 : "SUBDATASET_1_DESC", "Bathymetric gridded data", "SUBDATASETS");
392 :
393 1 : poDS->GDALDataset::SetMetadataItem(
394 : "SUBDATASET_2_NAME",
395 : CPLSPrintf("S102:\"%s\":QualityOfSurvey", osFilename.c_str()),
396 : "SUBDATASETS");
397 1 : poDS->GDALDataset::SetMetadataItem(
398 : "SUBDATASET_2_DESC", "Georeferenced metadata QualityOfSurvey",
399 : "SUBDATASETS");
400 : }
401 : }
402 :
403 : // Setup/check for pam .aux.xml.
404 7 : poDS->SetDescription(osFilename.c_str());
405 7 : poDS->TryLoadXML();
406 :
407 : // Setup overviews.
408 7 : poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
409 :
410 7 : return poDS.release();
411 : }
412 :
413 : /************************************************************************/
414 : /* OpenQualityOfSurvey() */
415 : /************************************************************************/
416 :
417 4 : bool S102Dataset::OpenQualityOfSurvey(
418 : GDALOpenInfo *poOpenInfo, const std::shared_ptr<GDALGroup> &poRootGroup)
419 : {
420 4 : const bool bNorthUp = CPLTestBool(
421 4 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "NORTH_UP", "YES"));
422 :
423 12 : auto poGroupQualityOfSurvey = poRootGroup->OpenGroup("QualityOfSurvey");
424 4 : if (!poGroupQualityOfSurvey)
425 : {
426 2 : CPLError(CE_Failure, CPLE_AppDefined,
427 : "Cannot find group /QualityOfSurvey");
428 2 : return false;
429 : }
430 :
431 : auto poGroupQualityOfSurvey01 =
432 6 : poGroupQualityOfSurvey->OpenGroup("QualityOfSurvey.01");
433 2 : if (!poGroupQualityOfSurvey01)
434 : {
435 0 : CPLError(CE_Failure, CPLE_AppDefined,
436 : "Cannot find group /QualityOfSurvey/QualityOfSurvey.01");
437 0 : return false;
438 : }
439 :
440 2 : if (auto poStartSequence =
441 4 : poGroupQualityOfSurvey01->GetAttribute("startSequence"))
442 : {
443 0 : const char *pszStartSequence = poStartSequence->ReadAsString();
444 0 : if (pszStartSequence && !EQUAL(pszStartSequence, "0,0"))
445 : {
446 0 : CPLError(CE_Failure, CPLE_AppDefined,
447 : "startSequence (=%s) != 0,0 is not supported",
448 : pszStartSequence);
449 0 : return false;
450 : }
451 : }
452 :
453 : // Compute geotransform
454 2 : m_bHasGT = S100GetGeoTransform(poGroupQualityOfSurvey01.get(),
455 2 : m_adfGeoTransform, bNorthUp);
456 :
457 6 : auto poGroup001 = poGroupQualityOfSurvey01->OpenGroup("Group_001");
458 2 : if (!poGroup001)
459 : {
460 0 : CPLError(
461 : CE_Failure, CPLE_AppDefined,
462 : "Cannot find group /QualityOfSurvey/QualityOfSurvey.01/Group_001");
463 0 : return false;
464 : }
465 :
466 6 : auto poValuesArray = poGroup001->OpenMDArray("values");
467 2 : if (!poValuesArray)
468 : {
469 0 : CPLError(CE_Failure, CPLE_AppDefined,
470 : "Cannot find array "
471 : "/QualityOfSurvey/QualityOfSurvey.01/Group_001/values");
472 0 : return false;
473 : }
474 :
475 : {
476 2 : const auto &oType = poValuesArray->GetDataType();
477 2 : if (oType.GetClass() != GEDTC_NUMERIC &&
478 0 : oType.GetNumericDataType() != GDT_UInt32)
479 : {
480 0 : CPLError(CE_Failure, CPLE_NotSupported,
481 : "Unsupported data type for %s",
482 0 : poValuesArray->GetFullName().c_str());
483 0 : return false;
484 : }
485 : }
486 :
487 2 : if (poValuesArray->GetDimensionCount() != 2)
488 : {
489 0 : CPLError(CE_Failure, CPLE_NotSupported,
490 : "Unsupported number of dimensions for %s",
491 0 : poValuesArray->GetFullName().c_str());
492 0 : return false;
493 : }
494 :
495 : auto poFeatureAttributeTable =
496 6 : poGroupQualityOfSurvey->OpenMDArray("featureAttributeTable");
497 2 : if (!poFeatureAttributeTable)
498 : {
499 0 : CPLError(CE_Failure, CPLE_AppDefined,
500 : "Cannot find array /QualityOfSurvey/featureAttributeTable");
501 0 : return false;
502 : }
503 :
504 : {
505 2 : const auto &oType = poFeatureAttributeTable->GetDataType();
506 2 : if (oType.GetClass() != GEDTC_COMPOUND)
507 : {
508 0 : CPLError(CE_Failure, CPLE_NotSupported,
509 : "Unsupported data type for %s",
510 0 : poFeatureAttributeTable->GetFullName().c_str());
511 0 : return false;
512 : }
513 :
514 2 : const auto &poComponents = oType.GetComponents();
515 2 : if (poComponents.size() >= 1 && poComponents[0]->GetName() != "id")
516 : {
517 0 : CPLError(CE_Failure, CPLE_AppDefined,
518 : "Missing 'id' component in %s",
519 0 : poFeatureAttributeTable->GetFullName().c_str());
520 0 : return false;
521 : }
522 : }
523 :
524 2 : if (bNorthUp)
525 1 : poValuesArray = poValuesArray->GetView("[::-1,...]");
526 :
527 : auto poDS =
528 4 : std::unique_ptr<GDALDataset>(poValuesArray->AsClassicDataset(1, 0));
529 :
530 2 : nRasterXSize = poDS->GetRasterXSize();
531 2 : nRasterYSize = poDS->GetRasterYSize();
532 :
533 : auto poRAT =
534 4 : HDF5CreateRAT(poFeatureAttributeTable, /* bFirstColIsMinMax = */ true);
535 : auto poBand = std::make_unique<S102GeoreferencedMetadataRasterBand>(
536 2 : std::move(poDS), std::move(poRAT));
537 2 : SetBand(1, poBand.release());
538 :
539 2 : return true;
540 : }
541 :
542 : /************************************************************************/
543 : /* S102DatasetDriverUnload() */
544 : /************************************************************************/
545 :
546 5 : static void S102DatasetDriverUnload(GDALDriver *)
547 : {
548 5 : HDF5UnloadFileDriver();
549 5 : }
550 :
551 : /************************************************************************/
552 : /* GDALRegister_S102() */
553 : /************************************************************************/
554 8 : void GDALRegister_S102()
555 :
556 : {
557 8 : if (!GDAL_CHECK_VERSION("S102"))
558 0 : return;
559 :
560 8 : if (GDALGetDriverByName(S102_DRIVER_NAME) != nullptr)
561 0 : return;
562 :
563 8 : GDALDriver *poDriver = new GDALDriver();
564 :
565 8 : S102DriverSetCommonMetadata(poDriver);
566 8 : poDriver->pfnOpen = S102Dataset::Open;
567 8 : poDriver->pfnUnloadDriver = S102DatasetDriverUnload;
568 :
569 8 : GetGDALDriverManager()->RegisterDriver(poDriver);
570 : }
|