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