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