Line data Source code
1 : /******************************************************************************
2 : *
3 : * Project: Hierarchical Data Format Release 5 (HDF5)
4 : * Purpose: Read S111 datasets.
5 : * Author: Even Rouault <even dot rouault at spatialys dot com>
6 : *
7 : ******************************************************************************
8 : * Copyright (c) 2023, Even Rouault <even dot rouault at spatialys dot com>
9 : *
10 : * SPDX-License-Identifier: MIT
11 : ****************************************************************************/
12 :
13 : #include "cpl_port.h"
14 : #include "hdf5dataset.h"
15 : #include "hdf5drivercore.h"
16 : #include "gh5_convenience.h"
17 : #include "rat.h"
18 : #include "s100.h"
19 :
20 : #include "gdal_frmts.h"
21 : #include "gdal_priv.h"
22 : #include "gdal_proxy.h"
23 : #include "gdal_rat.h"
24 :
25 : #include <limits>
26 : #include <map>
27 :
28 : /************************************************************************/
29 : /* S111Dataset */
30 : /************************************************************************/
31 :
32 14 : class S111Dataset final : public S100BaseDataset
33 : {
34 : public:
35 7 : explicit S111Dataset(const std::string &osFilename)
36 7 : : S100BaseDataset(osFilename)
37 : {
38 7 : }
39 :
40 : ~S111Dataset() override;
41 :
42 : static GDALDataset *Open(GDALOpenInfo *);
43 : };
44 :
45 : S111Dataset::~S111Dataset() = default;
46 :
47 : /************************************************************************/
48 : /* S111RasterBand */
49 : /************************************************************************/
50 :
51 : class S111RasterBand final : public GDALProxyRasterBand
52 : {
53 : friend class S111Dataset;
54 : std::unique_ptr<GDALDataset> m_poDS{};
55 : GDALRasterBand *m_poUnderlyingBand = nullptr;
56 : std::string m_osUnitType{};
57 : std::unique_ptr<GDALRasterAttributeTable> m_poRAT{};
58 :
59 : CPL_DISALLOW_COPY_ASSIGN(S111RasterBand)
60 :
61 : public:
62 8 : explicit S111RasterBand(std::unique_ptr<GDALDataset> &&poDSIn)
63 16 : : m_poDS(std::move(poDSIn)),
64 8 : m_poUnderlyingBand(m_poDS->GetRasterBand(1))
65 : {
66 8 : eDataType = m_poUnderlyingBand->GetRasterDataType();
67 8 : m_poUnderlyingBand->GetBlockSize(&nBlockXSize, &nBlockYSize);
68 8 : }
69 :
70 : GDALRasterBand *
71 : RefUnderlyingRasterBand(bool /*bForceOpen*/ = true) const override;
72 :
73 4 : const char *GetUnitType() override
74 : {
75 4 : return m_osUnitType.c_str();
76 : }
77 :
78 1 : GDALRasterAttributeTable *GetDefaultRAT() override
79 : {
80 1 : return m_poRAT.get();
81 : }
82 :
83 0 : char **GetMetadata(const char *pszDomain) override
84 : {
85 : // Short-circuit GDALProxyRasterBand...
86 0 : return GDALRasterBand::GetMetadata(pszDomain);
87 : }
88 : };
89 :
90 : GDALRasterBand *
91 12 : S111RasterBand::RefUnderlyingRasterBand(bool /*bForceOpen*/) const
92 : {
93 12 : return m_poUnderlyingBand;
94 : }
95 :
96 : /************************************************************************/
97 : /* Open() */
98 : /************************************************************************/
99 :
100 8 : GDALDataset *S111Dataset::Open(GDALOpenInfo *poOpenInfo)
101 :
102 : {
103 : // Confirm that this appears to be a S111 file.
104 8 : if (!S111DatasetIdentify(poOpenInfo))
105 0 : return nullptr;
106 :
107 : HDF5_GLOBAL_LOCK();
108 :
109 8 : if (poOpenInfo->nOpenFlags & GDAL_OF_MULTIDIM_RASTER)
110 : {
111 1 : return HDF5Dataset::OpenMultiDim(poOpenInfo);
112 : }
113 :
114 : // Confirm the requested access is supported.
115 7 : if (poOpenInfo->eAccess == GA_Update)
116 : {
117 0 : ReportUpdateNotSupportedByDriver("S111");
118 0 : return nullptr;
119 : }
120 :
121 14 : std::string osFilename(poOpenInfo->pszFilename);
122 14 : std::string osFeatureInstance = "SurfaceCurrent.01";
123 14 : std::string osGroup;
124 7 : if (STARTS_WITH(poOpenInfo->pszFilename, "S111:"))
125 : {
126 : const CPLStringList aosTokens(
127 5 : CSLTokenizeString2(poOpenInfo->pszFilename, ":",
128 5 : CSLT_HONOURSTRINGS | CSLT_PRESERVEESCAPES));
129 :
130 5 : if (aosTokens.size() == 2)
131 : {
132 0 : osFilename = aosTokens[1];
133 : }
134 5 : else if (aosTokens.size() == 3)
135 : {
136 3 : osFilename = aosTokens[1];
137 3 : osGroup = aosTokens[2];
138 : }
139 2 : else if (aosTokens.size() == 4)
140 : {
141 2 : osFilename = aosTokens[1];
142 2 : osFeatureInstance = aosTokens[2];
143 2 : osGroup = aosTokens[3];
144 : }
145 : else
146 : {
147 0 : return nullptr;
148 : }
149 : }
150 :
151 14 : auto poDS = std::make_unique<S111Dataset>(osFilename);
152 7 : if (!poDS->Init())
153 0 : return nullptr;
154 :
155 7 : const auto &poRootGroup = poDS->m_poRootGroup;
156 :
157 21 : auto poSurfaceCurrent = poRootGroup->OpenGroup("SurfaceCurrent");
158 7 : if (!poSurfaceCurrent)
159 : {
160 0 : CPLError(CE_Failure, CPLE_AppDefined,
161 : "Cannot find /SurfaceCurrent group");
162 0 : return nullptr;
163 : }
164 :
165 : auto poDataCodingFormat =
166 21 : poSurfaceCurrent->GetAttribute("dataCodingFormat");
167 14 : if (!poDataCodingFormat ||
168 7 : poDataCodingFormat->GetDataType().GetClass() != GEDTC_NUMERIC)
169 : {
170 0 : CPLError(CE_Failure, CPLE_AppDefined,
171 : "Cannot find /SurfaceCurrent/dataCodingFormat attribute");
172 0 : return nullptr;
173 : }
174 7 : const int nDataCodingFormat = poDataCodingFormat->ReadAsInt();
175 7 : if (nDataCodingFormat != 2)
176 : {
177 0 : CPLError(CE_Failure, CPLE_NotSupported,
178 : "dataCodingFormat=%d is not supported by the S111 driver",
179 : nDataCodingFormat);
180 0 : return nullptr;
181 : }
182 :
183 : // Read additional metadata
184 21 : for (const char *pszAttrName :
185 : {"methodCurrentsProduct", "minDatasetCurrentSpeed",
186 28 : "maxDatasetCurrentSpeed"})
187 : {
188 63 : auto poAttr = poSurfaceCurrent->GetAttribute(pszAttrName);
189 21 : if (poAttr)
190 : {
191 14 : const char *pszVal = poAttr->ReadAsString();
192 14 : if (pszVal)
193 : {
194 14 : poDS->GDALDataset::SetMetadataItem(pszAttrName, pszVal);
195 : }
196 : }
197 : }
198 :
199 7 : int nNumInstances = 1;
200 7 : if (osGroup.empty())
201 : {
202 6 : auto poNumInstances = poSurfaceCurrent->GetAttribute("numInstances");
203 3 : if (poNumInstances &&
204 3 : poNumInstances->GetDataType().GetClass() == GEDTC_NUMERIC)
205 : {
206 1 : nNumInstances = poNumInstances->ReadAsInt();
207 : }
208 : }
209 7 : if (nNumInstances != 1)
210 : {
211 2 : CPLStringList aosSubDSList;
212 1 : int iSubDS = 0;
213 2 : for (const std::string &featureInstanceName :
214 5 : poSurfaceCurrent->GetGroupNames())
215 : {
216 : auto poFeatureInstance =
217 4 : poSurfaceCurrent->OpenGroup(featureInstanceName);
218 2 : if (poFeatureInstance)
219 : {
220 4 : GDALMajorObject mo;
221 : // Read first vertical datum from root group and let the
222 : // coverage override it.
223 2 : S100ReadVerticalDatum(&mo, poRootGroup.get());
224 2 : S100ReadVerticalDatum(&mo, poFeatureInstance.get());
225 :
226 4 : const auto aosGroupNames = poFeatureInstance->GetGroupNames();
227 4 : for (const auto &osSubGroup : aosGroupNames)
228 : {
229 2 : if (auto poSubGroup =
230 4 : poFeatureInstance->OpenGroup(osSubGroup))
231 : {
232 2 : ++iSubDS;
233 : aosSubDSList.SetNameValue(
234 : CPLSPrintf("SUBDATASET_%d_NAME", iSubDS),
235 : CPLSPrintf("S111:\"%s\":%s:%s", osFilename.c_str(),
236 : featureInstanceName.c_str(),
237 2 : osSubGroup.c_str()));
238 :
239 4 : std::string verticalDatum;
240 : const char *pszValue =
241 2 : mo.GetMetadataItem(S100_VERTICAL_DATUM_MEANING);
242 2 : if (pszValue)
243 : {
244 2 : verticalDatum = ", vertical datum ";
245 2 : verticalDatum += pszValue;
246 : pszValue =
247 2 : mo.GetMetadataItem(S100_VERTICAL_DATUM_ABBREV);
248 2 : if (pszValue)
249 : {
250 2 : verticalDatum += " (";
251 2 : verticalDatum += pszValue;
252 2 : verticalDatum += ')';
253 : }
254 : }
255 : else
256 : {
257 : pszValue =
258 0 : mo.GetMetadataItem(S100_VERTICAL_DATUM_NAME);
259 0 : if (pszValue)
260 : {
261 0 : verticalDatum = ", vertical datum ";
262 0 : verticalDatum += pszValue;
263 : }
264 : }
265 :
266 4 : std::string osSubDSDesc;
267 : const auto poTimePoint =
268 6 : poSubGroup->GetAttribute("timePoint");
269 2 : if (poTimePoint)
270 : {
271 2 : const char *pszVal = poTimePoint->ReadAsString();
272 2 : if (pszVal)
273 : {
274 2 : osSubDSDesc = "Values for feature instance ";
275 2 : osSubDSDesc += featureInstanceName;
276 2 : osSubDSDesc += verticalDatum;
277 2 : osSubDSDesc += " at timestamp ";
278 2 : osSubDSDesc += pszVal;
279 : }
280 : }
281 2 : if (osSubDSDesc.empty())
282 : {
283 0 : osSubDSDesc = "Values for feature instance ";
284 0 : osSubDSDesc += featureInstanceName;
285 0 : osSubDSDesc += verticalDatum;
286 0 : osSubDSDesc += " and group ";
287 0 : osSubDSDesc += osSubGroup;
288 : }
289 :
290 : aosSubDSList.SetNameValue(
291 : CPLSPrintf("SUBDATASET_%d_DESC", iSubDS),
292 2 : osSubDSDesc.c_str());
293 : }
294 : }
295 : }
296 : }
297 :
298 1 : poDS->GDALDataset::SetMetadata(aosSubDSList.List(), "SUBDATASETS");
299 :
300 : // Setup/check for pam .aux.xml.
301 1 : poDS->SetDescription(osFilename.c_str());
302 1 : poDS->TryLoadXML();
303 :
304 : // Setup overviews.
305 1 : poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
306 :
307 1 : return poDS.release();
308 : }
309 :
310 12 : auto poFeatureInstance = poSurfaceCurrent->OpenGroup(osFeatureInstance);
311 6 : if (!poFeatureInstance)
312 : {
313 0 : CPLError(CE_Failure, CPLE_AppDefined,
314 : "Cannot find /SurfaceCurrent/%s group",
315 : osFeatureInstance.c_str());
316 0 : return nullptr;
317 : }
318 :
319 : // Read additional metadata
320 24 : for (const char *pszAttrName :
321 : {"timeRecordInterval", "dateTimeOfFirstRecord", "dateTimeOfLastRecord",
322 30 : "numberOfTimes"})
323 : {
324 72 : auto poAttr = poFeatureInstance->GetAttribute(pszAttrName);
325 24 : if (poAttr)
326 : {
327 24 : const char *pszVal = poAttr->ReadAsString();
328 24 : if (pszVal)
329 : {
330 24 : poDS->GDALDataset::SetMetadataItem(pszAttrName, pszVal);
331 : }
332 : }
333 : }
334 :
335 6 : if (auto poDataDynamicity =
336 18 : poFeatureInstance->GetAttribute("dataDynamicity"))
337 : {
338 2 : if (poDataDynamicity->GetDataType().GetClass() == GEDTC_NUMERIC)
339 : {
340 2 : const int nVal = poDataDynamicity->ReadAsInt();
341 : const std::map<int, const char *> oDataDynamicityMap = {
342 : {1, "Observation"},
343 : {2, "Astronomical prediction"},
344 : {3, "Analysis or hybrid method"},
345 : {4, "Hydrodynamic model hindcast"},
346 : {5, "Hydrodynamic model forecast"},
347 : {6, "Observed minus predicted"},
348 : {7, "Observed minus analysis"},
349 : {8, "Observed minus hindcast"},
350 : {9, "Observed minus forecast"},
351 : {10, "Forecast minus predicted"},
352 4 : };
353 2 : const auto oIter = oDataDynamicityMap.find(nVal);
354 2 : if (oIter != oDataDynamicityMap.end())
355 2 : poDS->GDALDataset::SetMetadataItem("DATA_DYNAMICITY_MEANING",
356 2 : oIter->second);
357 : }
358 : }
359 :
360 : // Read optional uncertainty array
361 13 : if (auto poUncertainty = poFeatureInstance->OpenMDArray("uncertainty"))
362 : {
363 1 : auto &apoDims = poUncertainty->GetDimensions();
364 2 : if (poUncertainty->GetDataType().GetClass() == GEDTC_COMPOUND &&
365 2 : apoDims.size() == 1 && apoDims[0]->GetSize() == 2)
366 : {
367 : const auto &oComponents =
368 1 : poUncertainty->GetDataType().GetComponents();
369 2 : if (oComponents.size() == 2 &&
370 2 : oComponents[0]->GetName() == "name" &&
371 2 : oComponents[0]->GetType().GetClass() == GEDTC_STRING &&
372 3 : oComponents[1]->GetName() == "value" &&
373 1 : oComponents[1]->GetType().GetNumericDataType() == GDT_Float64)
374 : {
375 3 : auto poName = poUncertainty->GetView("[\"name\"]");
376 3 : auto poValue = poUncertainty->GetView("[\"value\"]");
377 1 : if (poName && poValue)
378 : {
379 1 : char *apszStr[2] = {nullptr, nullptr};
380 1 : double adfVals[2] = {0, 0};
381 1 : GUInt64 arrayStartIdx[] = {0};
382 1 : size_t count[] = {2};
383 1 : GInt64 arrayStep[] = {1};
384 1 : GPtrDiff_t bufferStride[] = {1};
385 2 : if (poName->Read(arrayStartIdx, count, arrayStep,
386 1 : bufferStride, oComponents[0]->GetType(),
387 2 : apszStr) &&
388 2 : poValue->Read(arrayStartIdx, count, arrayStep,
389 1 : bufferStride, oComponents[1]->GetType(),
390 : adfVals))
391 : {
392 3 : for (int i = 0; i < 2; ++i)
393 : {
394 4 : std::string osName = apszStr[i];
395 2 : if (osName[0] >= 'a' && osName[0] <= 'z')
396 2 : osName[0] = osName[0] - 'a' + 'A';
397 2 : osName = "uncertainty" + osName;
398 2 : poDS->GDALDataset::SetMetadataItem(
399 : osName.c_str(), CPLSPrintf("%f", adfVals[i]));
400 : }
401 : }
402 1 : VSIFree(apszStr[0]);
403 1 : VSIFree(apszStr[1]);
404 : }
405 : }
406 : }
407 : }
408 :
409 12 : if (auto poStartSequence = poFeatureInstance->GetAttribute("startSequence"))
410 : {
411 6 : const char *pszStartSequence = poStartSequence->ReadAsString();
412 6 : if (pszStartSequence && !EQUAL(pszStartSequence, "0,0"))
413 : {
414 0 : CPLError(CE_Failure, CPLE_AppDefined,
415 : "startSequence (=%s) != 0,0 is not supported",
416 : pszStartSequence);
417 0 : return nullptr;
418 : }
419 : }
420 :
421 6 : if (!S100GetNumPointsLongitudinalLatitudinal(
422 6 : poFeatureInstance.get(), poDS->nRasterXSize, poDS->nRasterYSize))
423 : {
424 0 : return nullptr;
425 : }
426 :
427 : // Potentially override vertical datum
428 6 : S100ReadVerticalDatum(poDS.get(), poFeatureInstance.get());
429 :
430 6 : const bool bNorthUp = CPLTestBool(
431 6 : CSLFetchNameValueDef(poOpenInfo->papszOpenOptions, "NORTH_UP", "YES"));
432 :
433 : // Compute geotransform
434 12 : poDS->m_bHasGT =
435 6 : S100GetGeoTransform(poFeatureInstance.get(), poDS->m_gt, bNorthUp);
436 :
437 6 : if (osGroup.empty())
438 : {
439 2 : const auto aosGroupNames = poFeatureInstance->GetGroupNames();
440 1 : int iSubDS = 1;
441 2 : for (const auto &osSubGroup : aosGroupNames)
442 : {
443 2 : if (auto poSubGroup = poFeatureInstance->OpenGroup(osSubGroup))
444 : {
445 1 : poDS->GDALDataset::SetMetadataItem(
446 : CPLSPrintf("SUBDATASET_%d_NAME", iSubDS),
447 : CPLSPrintf("S111:\"%s\":%s", osFilename.c_str(),
448 : osSubGroup.c_str()),
449 : "SUBDATASETS");
450 2 : std::string osSubDSDesc = "Values for group ";
451 1 : osSubDSDesc += osSubGroup;
452 2 : const auto poTimePoint = poSubGroup->GetAttribute("timePoint");
453 1 : if (poTimePoint)
454 : {
455 1 : const char *pszVal = poTimePoint->ReadAsString();
456 1 : if (pszVal)
457 : {
458 1 : osSubDSDesc = "Values at timestamp ";
459 1 : osSubDSDesc += pszVal;
460 : }
461 : }
462 1 : poDS->GDALDataset::SetMetadataItem(
463 : CPLSPrintf("SUBDATASET_%d_DESC", iSubDS),
464 : osSubDSDesc.c_str(), "SUBDATASETS");
465 1 : ++iSubDS;
466 : }
467 : }
468 : }
469 : else
470 : {
471 5 : auto poGroup = poFeatureInstance->OpenGroup(osGroup);
472 5 : if (!poGroup)
473 : {
474 1 : CPLError(CE_Failure, CPLE_AppDefined,
475 : "Cannot find /SurfaceCurrent/%s/%s group",
476 : osFeatureInstance.c_str(), osGroup.c_str());
477 1 : return nullptr;
478 : }
479 :
480 8 : auto poValuesArray = poGroup->OpenMDArray("values");
481 4 : if (!poValuesArray)
482 : {
483 0 : CPLError(CE_Failure, CPLE_AppDefined,
484 : "Cannot find /SurfaceCurrent/%s/%s/values array",
485 : osFeatureInstance.c_str(), osGroup.c_str());
486 0 : return nullptr;
487 : }
488 :
489 4 : if (poValuesArray->GetDimensionCount() != 2)
490 : {
491 0 : CPLError(CE_Failure, CPLE_AppDefined,
492 : "Wrong dimension count for %s",
493 0 : poValuesArray->GetFullName().c_str());
494 0 : return nullptr;
495 : }
496 :
497 4 : const auto &oType = poValuesArray->GetDataType();
498 4 : if (oType.GetClass() != GEDTC_COMPOUND)
499 : {
500 0 : CPLError(CE_Failure, CPLE_AppDefined, "Wrong data type for %s",
501 0 : poValuesArray->GetFullName().c_str());
502 0 : return nullptr;
503 : }
504 :
505 4 : const auto &oComponents = oType.GetComponents();
506 8 : if (!(oComponents.size() == 2 &&
507 8 : ((oComponents[0]->GetName() == "surfaceCurrentSpeed" &&
508 4 : oComponents[0]->GetType().GetNumericDataType() == GDT_Float32 &&
509 8 : oComponents[1]->GetName() == "surfaceCurrentDirection" &&
510 4 : oComponents[1]->GetType().GetNumericDataType() ==
511 0 : GDT_Float32) ||
512 : // S111US_20170829.0100_W078.N44_F2_loofs_type2.h5 has direction first...
513 0 : (oComponents[0]->GetName() == "surfaceCurrentDirection" &&
514 0 : oComponents[0]->GetType().GetNumericDataType() == GDT_Float32 &&
515 0 : oComponents[1]->GetName() == "surfaceCurrentSpeed" &&
516 0 : oComponents[1]->GetType().GetNumericDataType() ==
517 : GDT_Float32))))
518 : {
519 0 : CPLError(CE_Failure, CPLE_AppDefined, "Wrong data type for %s",
520 0 : poValuesArray->GetFullName().c_str());
521 0 : return nullptr;
522 : }
523 :
524 4 : const auto &apoDims = poValuesArray->GetDimensions();
525 4 : if (apoDims[0]->GetSize() != static_cast<unsigned>(poDS->nRasterYSize))
526 : {
527 0 : CPLError(CE_Failure, CPLE_AppDefined,
528 : "numPointsLatitudinal(=%d) doesn't match first dimension "
529 : "size of %s (=%d)",
530 0 : poDS->nRasterYSize, poValuesArray->GetFullName().c_str(),
531 0 : static_cast<int>(apoDims[0]->GetSize()));
532 0 : return nullptr;
533 : }
534 4 : if (apoDims[1]->GetSize() != static_cast<unsigned>(poDS->nRasterXSize))
535 : {
536 0 : CPLError(CE_Failure, CPLE_AppDefined,
537 : "numPointsLongitudinal(=%d) doesn't match second "
538 : "dimension size of %s (=%d)",
539 0 : poDS->nRasterXSize, poValuesArray->GetFullName().c_str(),
540 0 : static_cast<int>(apoDims[1]->GetSize()));
541 0 : return nullptr;
542 : }
543 :
544 4 : if (bNorthUp)
545 3 : poValuesArray = poValuesArray->GetView("[::-1,...]");
546 :
547 : // Create surfaceCurrentSpeed band
548 : auto poSurfaceCurrentSpeed =
549 12 : poValuesArray->GetView("[\"surfaceCurrentSpeed\"]");
550 : auto poSurfaceCurrentSpeedDS = std::unique_ptr<GDALDataset>(
551 8 : poSurfaceCurrentSpeed->AsClassicDataset(1, 0));
552 : auto poSurfaceCurrentSpeedBand = std::make_unique<S111RasterBand>(
553 8 : std::move(poSurfaceCurrentSpeedDS));
554 4 : poSurfaceCurrentSpeedBand->SetDescription("surfaceCurrentSpeed");
555 4 : poSurfaceCurrentSpeedBand->m_osUnitType = "knots";
556 :
557 : // From S-111 v1.2 table 9.1 (Speed ranges) and 9.2 (Colour schemas)
558 8 : auto poRAT = std::make_unique<GDALDefaultRasterAttributeTable>();
559 4 : poRAT->CreateColumn("speed_band", GFT_Integer, GFU_Generic);
560 4 : poRAT->CreateColumn("min_speed", GFT_Real, GFU_Min);
561 4 : poRAT->CreateColumn("width_band", GFT_Real, GFU_Generic);
562 4 : poRAT->CreateColumn("color", GFT_String, GFU_Generic);
563 4 : poRAT->CreateColumn("red", GFT_Integer, GFU_RedMin);
564 4 : poRAT->CreateColumn("green", GFT_Integer, GFU_GreenMin);
565 4 : poRAT->CreateColumn("blue", GFT_Integer, GFU_BlueMin);
566 :
567 : const struct
568 : {
569 : int nSpeedBand;
570 : double dfMinSpeed;
571 : double dfWidthBand;
572 : const char *pszColor;
573 : int nRed;
574 : int nGreen;
575 : int nBlue;
576 4 : } aoRatValues[] = {
577 : {1, 0.0, 0.5, "purple", 118, 82, 226},
578 : {2, 0.5, 0.5, "dark blue", 72, 152, 211},
579 : {3, 1.0, 1.0, "light blue", 97, 203, 229},
580 : {4, 2.0, 1.0, "dark green", 109, 188, 69},
581 : {5, 3.0, 2.0, "light green", 180, 220, 0},
582 : {6, 5.0, 2.0, "yellow green", 205, 193, 0},
583 : {7, 7.0, 3.0, "orange", 248, 167, 24},
584 : {8, 10.0, 3.0, "pink", 247, 162, 157},
585 : {9, 13.0, 86.0, "red", 255, 30, 30},
586 : };
587 :
588 4 : int iRow = 0;
589 40 : for (const auto &oRecord : aoRatValues)
590 : {
591 36 : int iCol = 0;
592 36 : poRAT->SetValue(iRow, iCol++, oRecord.nSpeedBand);
593 36 : poRAT->SetValue(iRow, iCol++, oRecord.dfMinSpeed);
594 36 : poRAT->SetValue(iRow, iCol++, oRecord.dfWidthBand);
595 36 : poRAT->SetValue(iRow, iCol++, oRecord.pszColor);
596 36 : poRAT->SetValue(iRow, iCol++, oRecord.nRed);
597 36 : poRAT->SetValue(iRow, iCol++, oRecord.nGreen);
598 36 : poRAT->SetValue(iRow, iCol++, oRecord.nBlue);
599 36 : ++iRow;
600 : }
601 :
602 4 : poSurfaceCurrentSpeedBand->m_poRAT = std::move(poRAT);
603 :
604 4 : poDS->SetBand(1, poSurfaceCurrentSpeedBand.release());
605 :
606 : // Create surfaceCurrentDirection band
607 : auto poSurfaceCurrentDirection =
608 12 : poValuesArray->GetView("[\"surfaceCurrentDirection\"]");
609 : auto poSurfaceCurrentDirectionDS = std::unique_ptr<GDALDataset>(
610 8 : poSurfaceCurrentDirection->AsClassicDataset(1, 0));
611 : auto poSurfaceCurrentDirectionBand = std::make_unique<S111RasterBand>(
612 8 : std::move(poSurfaceCurrentDirectionDS));
613 4 : poSurfaceCurrentDirectionBand->SetDescription(
614 4 : "surfaceCurrentDirection");
615 4 : poSurfaceCurrentDirectionBand->m_osUnitType = "degree";
616 4 : poSurfaceCurrentDirectionBand->GDALRasterBand::SetMetadataItem(
617 : "ANGLE_CONVENTION", "From true north, clockwise");
618 4 : poDS->SetBand(2, poSurfaceCurrentDirectionBand.release());
619 : }
620 :
621 5 : poDS->GDALDataset::SetMetadataItem(GDALMD_AREA_OR_POINT, GDALMD_AOP_POINT);
622 :
623 : // Setup/check for pam .aux.xml.
624 5 : poDS->SetDescription(osFilename.c_str());
625 5 : poDS->TryLoadXML();
626 :
627 : // Setup overviews.
628 5 : poDS->oOvManager.Initialize(poDS.get(), osFilename.c_str());
629 :
630 5 : return poDS.release();
631 : }
632 :
633 : /************************************************************************/
634 : /* S111DatasetDriverUnload() */
635 : /************************************************************************/
636 :
637 323 : static void S111DatasetDriverUnload(GDALDriver *)
638 : {
639 323 : HDF5UnloadFileDriver();
640 323 : }
641 :
642 : /************************************************************************/
643 : /* GDALRegister_S111() */
644 : /************************************************************************/
645 355 : void GDALRegister_S111()
646 :
647 : {
648 355 : if (!GDAL_CHECK_VERSION("S111"))
649 0 : return;
650 :
651 355 : if (GDALGetDriverByName(S111_DRIVER_NAME) != nullptr)
652 0 : return;
653 :
654 355 : GDALDriver *poDriver = new GDALDriver();
655 :
656 355 : S111DriverSetCommonMetadata(poDriver);
657 355 : poDriver->pfnOpen = S111Dataset::Open;
658 355 : poDriver->pfnUnloadDriver = S111DatasetDriverUnload;
659 :
660 355 : GetGDALDriverManager()->RegisterDriver(poDriver);
661 : }
|