Line data Source code
1 : /******************************************************************************
2 : *
3 : * Purpose: ADRG reader
4 : * Author: Even Rouault, even.rouault at spatialys.com
5 : *
6 : ******************************************************************************
7 : * Copyright (c) 2007-2013, Even Rouault <even dot rouault at spatialys.com>
8 : *
9 : * SPDX-License-Identifier: MIT
10 : ****************************************************************************/
11 :
12 : #include "cpl_string.h"
13 : #include "gdal_pam.h"
14 : #include "gdal_frmts.h"
15 : #include "iso8211.h"
16 : #include "ogr_spatialref.h"
17 :
18 : #include <limits>
19 : #include <new>
20 :
21 : #define N_ELEMENTS(x) (sizeof(x) / sizeof(x[0]))
22 :
23 : #define DIGIT_ZERO '0'
24 :
25 : class ADRGDataset final : public GDALPamDataset
26 : {
27 : friend class ADRGRasterBand;
28 :
29 : CPLString osGENFileName;
30 : CPLString osIMGFileName;
31 : OGRSpatialReference m_oSRS{};
32 :
33 : VSILFILE *fdIMG;
34 : int *TILEINDEX;
35 : int offsetInIMG;
36 : int NFC;
37 : int NFL;
38 : double LSO;
39 : double PSO;
40 : int ARV;
41 : int BRV;
42 :
43 : char **papszSubDatasets;
44 :
45 : GDALGeoTransform m_gt{};
46 :
47 : static char **GetGENListFromTHF(const char *pszFileName);
48 : static char **GetIMGListFromGEN(const char *pszFileName,
49 : int *pnRecordIndex = nullptr);
50 : static ADRGDataset *OpenDataset(const char *pszGENFileName,
51 : const char *pszIMGFileName,
52 : DDFRecord *record = nullptr);
53 : static DDFRecord *FindRecordInGENForIMG(DDFModule &module,
54 : const char *pszGENFileName,
55 : const char *pszIMGFileName);
56 :
57 : public:
58 : ADRGDataset();
59 : ~ADRGDataset() override;
60 :
61 : const OGRSpatialReference *GetSpatialRef() const override;
62 : CPLErr GetGeoTransform(GDALGeoTransform >) const override;
63 :
64 : char **GetMetadataDomainList() override;
65 : char **GetMetadata(const char *pszDomain = "") override;
66 :
67 : char **GetFileList() override;
68 :
69 : void AddSubDataset(const char *pszGENFileName, const char *pszIMGFileName);
70 :
71 : static GDALDataset *Open(GDALOpenInfo *);
72 :
73 : static double GetLongitudeFromString(const char *str);
74 : static double GetLatitudeFromString(const char *str);
75 : };
76 :
77 : /************************************************************************/
78 : /* ==================================================================== */
79 : /* ADRGRasterBand */
80 : /* ==================================================================== */
81 : /************************************************************************/
82 :
83 : class ADRGRasterBand final : public GDALPamRasterBand
84 : {
85 : friend class ADRGDataset;
86 :
87 : public:
88 : ADRGRasterBand(ADRGDataset *, int);
89 :
90 : GDALColorInterp GetColorInterpretation() override;
91 : CPLErr IReadBlock(int, int, void *) override;
92 :
93 : double GetNoDataValue(int *pbSuccess = nullptr) override;
94 :
95 : // virtual int GetOverviewCount();
96 : // virtual GDALRasterBand* GetOverview(int i);
97 : };
98 :
99 : /************************************************************************/
100 : /* ADRGRasterBand() */
101 : /************************************************************************/
102 :
103 24 : ADRGRasterBand::ADRGRasterBand(ADRGDataset *poDSIn, int nBandIn)
104 :
105 : {
106 24 : poDS = poDSIn;
107 24 : nBand = nBandIn;
108 :
109 24 : eDataType = GDT_Byte;
110 :
111 24 : nBlockXSize = 128;
112 24 : nBlockYSize = 128;
113 24 : }
114 :
115 : /************************************************************************/
116 : /* GetNoDataValue() */
117 : /************************************************************************/
118 :
119 0 : double ADRGRasterBand::GetNoDataValue(int *pbSuccess)
120 : {
121 0 : if (pbSuccess)
122 0 : *pbSuccess = TRUE;
123 :
124 0 : return 0.0;
125 : }
126 :
127 : /************************************************************************/
128 : /* GetColorInterpretation() */
129 : /************************************************************************/
130 :
131 0 : GDALColorInterp ADRGRasterBand::GetColorInterpretation()
132 :
133 : {
134 0 : if (nBand == 1)
135 0 : return GCI_RedBand;
136 :
137 0 : else if (nBand == 2)
138 0 : return GCI_GreenBand;
139 :
140 0 : return GCI_BlueBand;
141 : }
142 :
143 : /************************************************************************/
144 : /* IReadBlock() */
145 : /************************************************************************/
146 :
147 4 : CPLErr ADRGRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
148 :
149 : {
150 4 : ADRGDataset *l_poDS = cpl::down_cast<ADRGDataset *>(poDS);
151 4 : int nBlock = nBlockYOff * l_poDS->NFC + nBlockXOff;
152 4 : if (nBlockXOff >= l_poDS->NFC || nBlockYOff >= l_poDS->NFL)
153 : {
154 0 : CPLError(CE_Failure, CPLE_AppDefined,
155 : "nBlockXOff=%d, NFC=%d, nBlockYOff=%d, NFL=%d", nBlockXOff,
156 : l_poDS->NFC, nBlockYOff, l_poDS->NFL);
157 0 : return CE_Failure;
158 : }
159 4 : CPLDebug("ADRG", "(%d,%d) -> nBlock = %d", nBlockXOff, nBlockYOff, nBlock);
160 :
161 : vsi_l_offset offset;
162 4 : if (l_poDS->TILEINDEX)
163 : {
164 4 : if (l_poDS->TILEINDEX[nBlock] <= 0)
165 : {
166 0 : memset(pImage, 0, 128 * 128);
167 0 : return CE_None;
168 : }
169 4 : offset = l_poDS->offsetInIMG +
170 4 : static_cast<vsi_l_offset>(l_poDS->TILEINDEX[nBlock] - 1) *
171 4 : 128 * 128 * 3 +
172 4 : (nBand - 1) * 128 * 128;
173 : }
174 : else
175 0 : offset = l_poDS->offsetInIMG +
176 0 : static_cast<vsi_l_offset>(nBlock) * 128 * 128 * 3 +
177 0 : (nBand - 1) * 128 * 128;
178 :
179 4 : if (VSIFSeekL(l_poDS->fdIMG, offset, SEEK_SET) != 0)
180 : {
181 0 : CPLError(CE_Failure, CPLE_FileIO,
182 : "Cannot seek to offset " CPL_FRMT_GUIB, offset);
183 0 : return CE_Failure;
184 : }
185 4 : if (VSIFReadL(pImage, 1, 128 * 128, l_poDS->fdIMG) != 128 * 128)
186 : {
187 0 : CPLError(CE_Failure, CPLE_FileIO,
188 : "Cannot read data at offset " CPL_FRMT_GUIB, offset);
189 0 : return CE_Failure;
190 : }
191 :
192 4 : return CE_None;
193 : }
194 :
195 : /************************************************************************/
196 : /* ADRGDataset() */
197 : /************************************************************************/
198 :
199 9 : ADRGDataset::ADRGDataset()
200 : : fdIMG(nullptr), TILEINDEX(nullptr), offsetInIMG(0), NFC(0), NFL(0),
201 9 : LSO(0.0), PSO(0.0), ARV(0), BRV(0), papszSubDatasets(nullptr)
202 : {
203 9 : m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
204 9 : }
205 :
206 : /************************************************************************/
207 : /* ~ADRGDataset() */
208 : /************************************************************************/
209 :
210 18 : ADRGDataset::~ADRGDataset()
211 : {
212 9 : CSLDestroy(papszSubDatasets);
213 :
214 9 : if (fdIMG)
215 : {
216 8 : VSIFCloseL(fdIMG);
217 : }
218 :
219 9 : if (TILEINDEX)
220 : {
221 8 : delete[] TILEINDEX;
222 : }
223 18 : }
224 :
225 : /************************************************************************/
226 : /* GetFileList() */
227 : /************************************************************************/
228 :
229 3 : char **ADRGDataset::GetFileList()
230 : {
231 3 : char **papszFileList = GDALPamDataset::GetFileList();
232 :
233 3 : if (!osGENFileName.empty() && !osIMGFileName.empty())
234 : {
235 3 : CPLString osMainFilename = GetDescription();
236 : VSIStatBufL sStat;
237 :
238 3 : const bool bMainFileReal = VSIStatL(osMainFilename, &sStat) == 0;
239 3 : if (bMainFileReal)
240 : {
241 4 : CPLString osShortMainFilename = CPLGetFilename(osMainFilename);
242 4 : CPLString osShortGENFileName = CPLGetFilename(osGENFileName);
243 2 : if (!EQUAL(osShortMainFilename.c_str(), osShortGENFileName.c_str()))
244 : papszFileList =
245 1 : CSLAddString(papszFileList, osGENFileName.c_str());
246 : }
247 : else
248 1 : papszFileList = CSLAddString(papszFileList, osGENFileName.c_str());
249 :
250 3 : papszFileList = CSLAddString(papszFileList, osIMGFileName.c_str());
251 : }
252 :
253 3 : return papszFileList;
254 : }
255 :
256 : /************************************************************************/
257 : /* AddSubDataset() */
258 : /************************************************************************/
259 :
260 2 : void ADRGDataset::AddSubDataset(const char *pszGENFileName,
261 : const char *pszIMGFileName)
262 : {
263 : char szName[80];
264 2 : int nCount = CSLCount(papszSubDatasets) / 2;
265 :
266 2 : CPLString osSubDatasetName;
267 2 : osSubDatasetName = "ADRG:";
268 2 : osSubDatasetName += pszGENFileName;
269 2 : osSubDatasetName += ",";
270 2 : osSubDatasetName += pszIMGFileName;
271 :
272 2 : snprintf(szName, sizeof(szName), "SUBDATASET_%d_NAME", nCount + 1);
273 2 : papszSubDatasets =
274 2 : CSLSetNameValue(papszSubDatasets, szName, osSubDatasetName);
275 :
276 2 : snprintf(szName, sizeof(szName), "SUBDATASET_%d_DESC", nCount + 1);
277 2 : papszSubDatasets =
278 2 : CSLSetNameValue(papszSubDatasets, szName, osSubDatasetName);
279 2 : }
280 :
281 : /************************************************************************/
282 : /* GetMetadataDomainList() */
283 : /************************************************************************/
284 :
285 0 : char **ADRGDataset::GetMetadataDomainList()
286 : {
287 0 : return BuildMetadataDomainList(GDALPamDataset::GetMetadataDomainList(),
288 0 : TRUE, "SUBDATASETS", nullptr);
289 : }
290 :
291 : /************************************************************************/
292 : /* GetMetadata() */
293 : /************************************************************************/
294 :
295 1 : char **ADRGDataset::GetMetadata(const char *pszDomain)
296 :
297 : {
298 1 : if (pszDomain != nullptr && EQUAL(pszDomain, "SUBDATASETS"))
299 0 : return papszSubDatasets;
300 :
301 1 : return GDALPamDataset::GetMetadata(pszDomain);
302 : }
303 :
304 : /************************************************************************/
305 : /* GetSpatialRef() */
306 : /************************************************************************/
307 :
308 2 : const OGRSpatialReference *ADRGDataset::GetSpatialRef() const
309 : {
310 2 : return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
311 : }
312 :
313 : /************************************************************************/
314 : /* GetGeoTransform() */
315 : /************************************************************************/
316 :
317 2 : CPLErr ADRGDataset::GetGeoTransform(GDALGeoTransform >) const
318 : {
319 2 : if (papszSubDatasets != nullptr)
320 0 : return CE_Failure;
321 :
322 2 : gt = m_gt;
323 :
324 2 : return CE_None;
325 : }
326 :
327 : /************************************************************************/
328 : /* GetLongitudeFromString() */
329 : /************************************************************************/
330 :
331 8 : double ADRGDataset::GetLongitudeFromString(const char *str)
332 : {
333 8 : char ddd[3 + 1] = {0};
334 8 : char mm[2 + 1] = {0};
335 8 : char ssdotss[5 + 1] = {0};
336 8 : int sign = (str[0] == '+') ? 1 : -1;
337 8 : str++;
338 8 : strncpy(ddd, str, 3);
339 8 : str += 3;
340 8 : strncpy(mm, str, 2);
341 8 : str += 2;
342 8 : strncpy(ssdotss, str, 5);
343 8 : return sign * (CPLAtof(ddd) + CPLAtof(mm) / 60 + CPLAtof(ssdotss) / 3600);
344 : }
345 :
346 : /************************************************************************/
347 : /* GetLatitudeFromString() */
348 : /************************************************************************/
349 :
350 8 : double ADRGDataset::GetLatitudeFromString(const char *str)
351 : {
352 8 : char ddd[2 + 1] = {0};
353 8 : char mm[2 + 1] = {0};
354 8 : char ssdotss[5 + 1] = {0};
355 8 : int sign = (str[0] == '+') ? 1 : -1;
356 8 : str++;
357 8 : strncpy(ddd, str, 2);
358 8 : str += 2;
359 8 : strncpy(mm, str, 2);
360 8 : str += 2;
361 8 : strncpy(ssdotss, str, 5);
362 8 : return sign * (CPLAtof(ddd) + CPLAtof(mm) / 60 + CPLAtof(ssdotss) / 3600);
363 : }
364 :
365 : /************************************************************************/
366 : /* FindRecordInGENForIMG() */
367 : /************************************************************************/
368 :
369 2 : DDFRecord *ADRGDataset::FindRecordInGENForIMG(DDFModule &module,
370 : const char *pszGENFileName,
371 : const char *pszIMGFileName)
372 : {
373 : /* Finds the GEN file corresponding to the IMG file */
374 2 : if (!module.Open(pszGENFileName, TRUE))
375 0 : return nullptr;
376 :
377 4 : CPLString osShortIMGFilename = CPLGetFilename(pszIMGFileName);
378 :
379 : /* Now finds the record */
380 : while (true)
381 : {
382 7 : CPLPushErrorHandler(CPLQuietErrorHandler);
383 7 : DDFRecord *record = module.ReadRecord();
384 7 : CPLPopErrorHandler();
385 7 : CPLErrorReset();
386 7 : if (record == nullptr)
387 0 : return nullptr;
388 :
389 7 : if (record->GetFieldCount() >= 5)
390 : {
391 5 : DDFField *field = record->GetField(0);
392 5 : DDFFieldDefn *fieldDefn = field->GetFieldDefn();
393 10 : if (!(strcmp(fieldDefn->GetName(), "001") == 0 &&
394 5 : fieldDefn->GetSubfieldCount() == 2))
395 : {
396 2 : continue;
397 : }
398 :
399 5 : const char *RTY = record->GetStringSubfield("001", 0, "RTY", 0);
400 5 : if (RTY == nullptr)
401 0 : continue;
402 : /* Ignore overviews */
403 5 : if (strcmp(RTY, "OVV") == 0)
404 2 : continue;
405 :
406 3 : if (strcmp(RTY, "GIN") != 0)
407 0 : continue;
408 :
409 3 : field = record->GetField(3);
410 3 : fieldDefn = field->GetFieldDefn();
411 :
412 6 : if (!(strcmp(fieldDefn->GetName(), "SPR") == 0 &&
413 3 : fieldDefn->GetSubfieldCount() == 15))
414 : {
415 0 : continue;
416 : }
417 :
418 3 : const char *pszBAD = record->GetStringSubfield("SPR", 0, "BAD", 0);
419 3 : if (pszBAD == nullptr || strlen(pszBAD) != 12)
420 0 : continue;
421 3 : CPLString osBAD = pszBAD;
422 : {
423 3 : char *c = (char *)strchr(osBAD.c_str(), ' ');
424 3 : if (c)
425 0 : *c = 0;
426 : }
427 :
428 3 : if (EQUAL(osShortIMGFilename.c_str(), osBAD.c_str()))
429 : {
430 2 : return record;
431 : }
432 : }
433 5 : }
434 : }
435 :
436 : /************************************************************************/
437 : /* OpenDataset() */
438 : /************************************************************************/
439 :
440 8 : ADRGDataset *ADRGDataset::OpenDataset(const char *pszGENFileName,
441 : const char *pszIMGFileName,
442 : DDFRecord *record)
443 : {
444 16 : DDFModule module;
445 :
446 8 : int SCA = 0;
447 8 : int ZNA = 0;
448 : double PSP;
449 : int ARV;
450 : int BRV;
451 : double LSO;
452 : double PSO;
453 : int NFL;
454 : int NFC;
455 16 : CPLString osBAD;
456 : int TIF;
457 8 : int *TILEINDEX = nullptr;
458 :
459 8 : if (record == nullptr)
460 : {
461 2 : record = FindRecordInGENForIMG(module, pszGENFileName, pszIMGFileName);
462 2 : if (record == nullptr)
463 0 : return nullptr;
464 : }
465 :
466 8 : DDFField *field = record->GetField(1);
467 8 : if (field == nullptr)
468 0 : return nullptr;
469 8 : DDFFieldDefn *fieldDefn = field->GetFieldDefn();
470 :
471 16 : if (!(strcmp(fieldDefn->GetName(), "DSI") == 0 &&
472 8 : fieldDefn->GetSubfieldCount() == 2))
473 : {
474 0 : return nullptr;
475 : }
476 :
477 8 : const char *pszPTR = record->GetStringSubfield("DSI", 0, "PRT", 0);
478 8 : if (pszPTR == nullptr || !EQUAL(pszPTR, "ADRG"))
479 0 : return nullptr;
480 :
481 8 : const char *pszNAM = record->GetStringSubfield("DSI", 0, "NAM", 0);
482 8 : if (pszNAM == nullptr || strlen(pszNAM) != 8)
483 0 : return nullptr;
484 16 : CPLString osNAM = pszNAM;
485 :
486 8 : field = record->GetField(2);
487 8 : if (field == nullptr)
488 0 : return nullptr;
489 8 : fieldDefn = field->GetFieldDefn();
490 :
491 : // TODO: Support on GIN things. And what is GIN?
492 : // GIN might mean general information and might be a typo of GEN.
493 : // if( isGIN )
494 : {
495 16 : if (!(strcmp(fieldDefn->GetName(), "GEN") == 0 &&
496 8 : fieldDefn->GetSubfieldCount() == 21))
497 : {
498 0 : return nullptr;
499 : }
500 :
501 8 : if (record->GetIntSubfield("GEN", 0, "STR", 0) != 3)
502 0 : return nullptr;
503 :
504 8 : SCA = record->GetIntSubfield("GEN", 0, "SCA", 0);
505 8 : CPLDebug("ADRG", "SCA=%d", SCA);
506 :
507 8 : ZNA = record->GetIntSubfield("GEN", 0, "ZNA", 0);
508 8 : CPLDebug("ADRG", "ZNA=%d", ZNA);
509 :
510 8 : PSP = record->GetFloatSubfield("GEN", 0, "PSP", 0);
511 8 : CPLDebug("ADRG", "PSP=%f", PSP);
512 :
513 8 : ARV = record->GetIntSubfield("GEN", 0, "ARV", 0);
514 8 : CPLDebug("ADRG", "ARV=%d", ARV);
515 :
516 8 : BRV = record->GetIntSubfield("GEN", 0, "BRV", 0);
517 8 : CPLDebug("ADRG", "BRV=%d", BRV);
518 8 : if (ARV <= 0 || (ZNA != 9 && ZNA != 18 && BRV <= 0))
519 0 : return nullptr;
520 :
521 8 : const char *pszLSO = record->GetStringSubfield("GEN", 0, "LSO", 0);
522 8 : if (pszLSO == nullptr || strlen(pszLSO) != 11)
523 0 : return nullptr;
524 8 : LSO = GetLongitudeFromString(pszLSO);
525 8 : CPLDebug("ADRG", "LSO=%f", LSO);
526 :
527 8 : const char *pszPSO = record->GetStringSubfield("GEN", 0, "PSO", 0);
528 8 : if (pszPSO == nullptr || strlen(pszPSO) != 10)
529 0 : return nullptr;
530 8 : PSO = GetLatitudeFromString(pszPSO);
531 8 : CPLDebug("ADRG", "PSO=%f", PSO);
532 : }
533 : #if 0
534 : else
535 : {
536 : if( !(strcmp(fieldDefn->GetName(), "OVI") == 0 &&
537 : fieldDefn->GetSubfieldCount() == 5) )
538 : {
539 : return NULL;
540 : }
541 :
542 : if( record->GetIntSubfield("OVI", 0, "STR", 0) != 3 )
543 : return NULL;
544 :
545 : ARV = record->GetIntSubfield("OVI", 0, "ARV", 0);
546 : CPLDebug("ADRG", "ARV=%d", ARV);
547 :
548 : BRV = record->GetIntSubfield("OVI", 0, "BRV", 0);
549 : CPLDebug("ADRG", "BRV=%d", BRV);
550 :
551 : const char* pszLSO = record->GetStringSubfield("OVI", 0, "LSO", 0);
552 : if( pszLSO == NULL || strlen(pszLSO) != 11 )
553 : return NULL;
554 : LSO = GetLongitudeFromString(pszLSO);
555 : CPLDebug("ADRG", "LSO=%f", LSO);
556 :
557 : const char* pszPSO = record->GetStringSubfield("OVI", 0, "PSO", 0);
558 : if( pszPSO == NULL || strlen(pszPSO) != 10 )
559 : return NULL;
560 : PSO = GetLatitudeFromString(pszPSO);
561 : CPLDebug("ADRG", "PSO=%f", PSO);
562 : }
563 : #endif
564 :
565 8 : field = record->GetField(3);
566 8 : if (field == nullptr)
567 0 : return nullptr;
568 8 : fieldDefn = field->GetFieldDefn();
569 :
570 16 : if (!(strcmp(fieldDefn->GetName(), "SPR") == 0 &&
571 8 : fieldDefn->GetSubfieldCount() == 15))
572 : {
573 0 : return nullptr;
574 : }
575 :
576 8 : NFL = record->GetIntSubfield("SPR", 0, "NFL", 0);
577 8 : CPLDebug("ADRG", "NFL=%d", NFL);
578 :
579 8 : NFC = record->GetIntSubfield("SPR", 0, "NFC", 0);
580 8 : CPLDebug("ADRG", "NFC=%d", NFC);
581 :
582 8 : const auto knIntMax = std::numeric_limits<int>::max();
583 8 : if (NFL <= 0 || NFC <= 0 || NFL > knIntMax / 128 || NFC > knIntMax / 128 ||
584 8 : NFL > (knIntMax - 1) / (NFC * 5))
585 : {
586 0 : CPLError(CE_Failure, CPLE_AppDefined, "Invalid NFL / NFC values");
587 0 : return nullptr;
588 : }
589 :
590 8 : int PNC = record->GetIntSubfield("SPR", 0, "PNC", 0);
591 8 : CPLDebug("ADRG", "PNC=%d", PNC);
592 8 : if (PNC != 128)
593 : {
594 0 : return nullptr;
595 : }
596 :
597 8 : int PNL = record->GetIntSubfield("SPR", 0, "PNL", 0);
598 8 : CPLDebug("ADRG", "PNL=%d", PNL);
599 8 : if (PNL != 128)
600 : {
601 0 : return nullptr;
602 : }
603 :
604 8 : const char *pszBAD = record->GetStringSubfield("SPR", 0, "BAD", 0);
605 8 : if (pszBAD == nullptr || strlen(pszBAD) != 12)
606 0 : return nullptr;
607 8 : osBAD = pszBAD;
608 : {
609 8 : char *c = (char *)strchr(osBAD.c_str(), ' ');
610 8 : if (c)
611 0 : *c = 0;
612 : }
613 8 : CPLDebug("ADRG", "BAD=%s", osBAD.c_str());
614 :
615 8 : const DDFSubfieldDefn *subfieldDefn = fieldDefn->GetSubfield(14);
616 16 : if (!(strcmp(subfieldDefn->GetName(), "TIF") == 0 &&
617 8 : (subfieldDefn->GetFormat())[0] == 'A'))
618 : {
619 0 : return nullptr;
620 : }
621 :
622 8 : const char *pszTIF = record->GetStringSubfield("SPR", 0, "TIF", 0);
623 8 : if (pszTIF == nullptr)
624 0 : return nullptr;
625 8 : TIF = pszTIF[0] == 'Y';
626 8 : CPLDebug("ADRG", "TIF=%d", TIF);
627 :
628 8 : if (TIF)
629 : {
630 8 : if (record->GetFieldCount() != 6)
631 : {
632 0 : return nullptr;
633 : }
634 :
635 8 : field = record->GetField(5);
636 8 : if (field == nullptr)
637 0 : return nullptr;
638 8 : fieldDefn = field->GetFieldDefn();
639 :
640 8 : if (!(strcmp(fieldDefn->GetName(), "TIM") == 0))
641 : {
642 0 : return nullptr;
643 : }
644 :
645 8 : if (field->GetDataSize() != 5 * NFL * NFC + 1)
646 : {
647 0 : return nullptr;
648 : }
649 :
650 : try
651 : {
652 8 : TILEINDEX = new int[NFL * NFC];
653 : }
654 0 : catch (const std::exception &)
655 : {
656 0 : return nullptr;
657 : }
658 8 : const char *ptr = field->GetData();
659 8 : char offset[5 + 1] = {0};
660 16 : for (int i = 0; i < NFL * NFC; i++)
661 : {
662 8 : strncpy(offset, ptr, 5);
663 8 : ptr += 5;
664 8 : TILEINDEX[i] = atoi(offset);
665 : // CPLDebug("ADRG", "TSI[%d]=%d", i, TILEINDEX[i]);
666 : }
667 : }
668 :
669 8 : VSILFILE *fdIMG = VSIFOpenL(pszIMGFileName, "rb");
670 8 : if (fdIMG == nullptr)
671 : {
672 0 : CPLError(CE_Failure, CPLE_AppDefined, "Cannot open %s\n",
673 : pszIMGFileName);
674 0 : delete[] TILEINDEX;
675 0 : return nullptr;
676 : }
677 :
678 : /* Skip ISO8211 header of IMG file */
679 8 : int offsetInIMG = 0;
680 : char c;
681 : char recordName[3];
682 8 : if (VSIFReadL(&c, 1, 1, fdIMG) != 1)
683 : {
684 0 : VSIFCloseL(fdIMG);
685 0 : delete[] TILEINDEX;
686 0 : return nullptr;
687 : }
688 2064 : while (!VSIFEofL(fdIMG))
689 : {
690 2064 : if (c == 30)
691 : {
692 48 : if (VSIFReadL(recordName, 1, 3, fdIMG) != 3)
693 : {
694 0 : VSIFCloseL(fdIMG);
695 0 : delete[] TILEINDEX;
696 0 : return nullptr;
697 : }
698 48 : offsetInIMG += 3;
699 48 : if (STARTS_WITH(recordName, "IMG"))
700 : {
701 8 : offsetInIMG += 4;
702 16 : if (VSIFSeekL(fdIMG, 3, SEEK_CUR) != 0 ||
703 8 : VSIFReadL(&c, 1, 1, fdIMG) != 1)
704 : {
705 0 : VSIFCloseL(fdIMG);
706 0 : delete[] TILEINDEX;
707 0 : return nullptr;
708 : }
709 14152 : while (c == ' ')
710 : {
711 14144 : offsetInIMG++;
712 14144 : if (VSIFReadL(&c, 1, 1, fdIMG) != 1)
713 : {
714 0 : VSIFCloseL(fdIMG);
715 0 : delete[] TILEINDEX;
716 0 : return nullptr;
717 : }
718 : }
719 8 : offsetInIMG++;
720 8 : break;
721 : }
722 : }
723 :
724 2056 : offsetInIMG++;
725 2056 : if (VSIFReadL(&c, 1, 1, fdIMG) != 1)
726 : {
727 0 : VSIFCloseL(fdIMG);
728 0 : delete[] TILEINDEX;
729 0 : return nullptr;
730 : }
731 : }
732 :
733 8 : if (VSIFEofL(fdIMG))
734 : {
735 0 : VSIFCloseL(fdIMG);
736 0 : delete[] TILEINDEX;
737 0 : return nullptr;
738 : }
739 :
740 8 : CPLDebug("ADRG", "Img offset data = %d", offsetInIMG);
741 :
742 8 : ADRGDataset *poDS = new ADRGDataset();
743 :
744 8 : poDS->osGENFileName = pszGENFileName;
745 8 : poDS->osIMGFileName = pszIMGFileName;
746 8 : poDS->NFC = NFC;
747 8 : poDS->NFL = NFL;
748 8 : poDS->nRasterXSize = NFC * 128;
749 8 : poDS->nRasterYSize = NFL * 128;
750 8 : poDS->LSO = LSO;
751 8 : poDS->PSO = PSO;
752 8 : poDS->ARV = ARV;
753 8 : poDS->BRV = BRV;
754 8 : poDS->TILEINDEX = TILEINDEX;
755 8 : poDS->fdIMG = fdIMG;
756 8 : poDS->offsetInIMG = offsetInIMG;
757 :
758 8 : if (ZNA == 9)
759 : {
760 : // North Polar Case
761 1 : poDS->m_gt[0] = 111319.4907933 * (90.0 - PSO) * sin(LSO * M_PI / 180.0);
762 1 : poDS->m_gt[1] = 40075016.68558 / ARV;
763 1 : poDS->m_gt[2] = 0.0;
764 2 : poDS->m_gt[3] =
765 1 : -111319.4907933 * (90.0 - PSO) * cos(LSO * M_PI / 180.0);
766 1 : poDS->m_gt[4] = 0.0;
767 1 : poDS->m_gt[5] = -40075016.68558 / ARV;
768 1 : poDS->m_oSRS.importFromWkt(
769 : "PROJCS[\"ARC_System_Zone_09\",GEOGCS[\"GCS_Sphere\","
770 : "DATUM[\"D_Sphere\",SPHEROID[\"Sphere\",6378137.0,0.0]],"
771 : "PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]],"
772 : "PROJECTION[\"Azimuthal_Equidistant\"],"
773 : "PARAMETER[\"latitude_of_center\",90],"
774 : "PARAMETER[\"longitude_of_center\",0],"
775 : "PARAMETER[\"false_easting\",0],"
776 : "PARAMETER[\"false_northing\",0],"
777 : "UNIT[\"metre\",1]]");
778 : }
779 7 : else if (ZNA == 18)
780 : {
781 : // South Polar Case
782 1 : poDS->m_gt[0] = 111319.4907933 * (90.0 + PSO) * sin(LSO * M_PI / 180.0);
783 1 : poDS->m_gt[1] = 40075016.68558 / ARV;
784 1 : poDS->m_gt[2] = 0.0;
785 1 : poDS->m_gt[3] = 111319.4907933 * (90.0 + PSO) * cos(LSO * M_PI / 180.0);
786 1 : poDS->m_gt[4] = 0.0;
787 1 : poDS->m_gt[5] = -40075016.68558 / ARV;
788 1 : poDS->m_oSRS.importFromWkt(
789 : "PROJCS[\"ARC_System_Zone_18\",GEOGCS[\"GCS_Sphere\","
790 : "DATUM[\"D_Sphere\",SPHEROID[\"Sphere\",6378137.0,0.0]],"
791 : "PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]],"
792 : "PROJECTION[\"Azimuthal_Equidistant\"],"
793 : "PARAMETER[\"latitude_of_center\",-90],"
794 : "PARAMETER[\"longitude_of_center\",0],"
795 : "PARAMETER[\"false_easting\",0],"
796 : "PARAMETER[\"false_northing\",0],"
797 : "UNIT[\"metre\",1]]");
798 : }
799 : else
800 : {
801 6 : poDS->m_gt[0] = LSO;
802 6 : poDS->m_gt[1] = 360. / ARV;
803 6 : poDS->m_gt[2] = 0.0;
804 6 : poDS->m_gt[3] = PSO;
805 6 : poDS->m_gt[4] = 0.0;
806 6 : poDS->m_gt[5] = -360. / BRV;
807 6 : poDS->m_oSRS.importFromWkt(SRS_WKT_WGS84_LAT_LONG);
808 : }
809 :
810 : // if( isGIN )
811 : {
812 : char szValue[32];
813 8 : snprintf(szValue, sizeof(szValue), "%d", SCA);
814 8 : poDS->SetMetadataItem("ADRG_SCA", szValue);
815 8 : snprintf(szValue, sizeof(szValue), "%d", ZNA);
816 8 : poDS->SetMetadataItem("ADRG_ZNA", szValue);
817 : }
818 :
819 8 : poDS->SetMetadataItem("ADRG_NAM", osNAM.c_str());
820 :
821 8 : poDS->nBands = 3;
822 32 : for (int i = 0; i < poDS->nBands; i++)
823 24 : poDS->SetBand(i + 1, new ADRGRasterBand(poDS, i + 1));
824 :
825 8 : return poDS;
826 : }
827 :
828 : /************************************************************************/
829 : /* GetGENListFromTHF() */
830 : /************************************************************************/
831 :
832 7 : char **ADRGDataset::GetGENListFromTHF(const char *pszFileName)
833 : {
834 14 : DDFModule module;
835 7 : DDFRecord *record = nullptr;
836 7 : int nFilenames = 0;
837 7 : char **papszFileNames = nullptr;
838 :
839 7 : if (!module.Open(pszFileName, TRUE))
840 0 : return papszFileNames;
841 :
842 : while (true)
843 : {
844 27 : CPLPushErrorHandler(CPLQuietErrorHandler);
845 27 : record = module.ReadRecord();
846 27 : CPLPopErrorHandler();
847 27 : CPLErrorReset();
848 27 : if (record == nullptr)
849 7 : break;
850 :
851 20 : if (record->GetFieldCount() >= 2)
852 : {
853 20 : DDFField *field = record->GetField(0);
854 20 : DDFFieldDefn *fieldDefn = field->GetFieldDefn();
855 40 : if (!(strcmp(fieldDefn->GetName(), "001") == 0 &&
856 20 : fieldDefn->GetSubfieldCount() == 2))
857 : {
858 0 : continue;
859 : }
860 :
861 20 : const char *RTY = record->GetStringSubfield("001", 0, "RTY", 0);
862 20 : if (RTY == nullptr || !(strcmp(RTY, "TFN") == 0))
863 : {
864 17 : continue;
865 : }
866 :
867 3 : int iVFFFieldInstance = 0;
868 16 : for (int i = 1; i < record->GetFieldCount(); i++)
869 : {
870 13 : field = record->GetField(i);
871 13 : fieldDefn = field->GetFieldDefn();
872 :
873 26 : if (!(strcmp(fieldDefn->GetName(), "VFF") == 0 &&
874 13 : fieldDefn->GetSubfieldCount() == 1))
875 : {
876 0 : continue;
877 : }
878 :
879 13 : const char *pszVFF = record->GetStringSubfield(
880 : "VFF", iVFFFieldInstance++, "VFF", 0);
881 13 : if (pszVFF == nullptr)
882 0 : continue;
883 13 : CPLString osSubFileName(pszVFF);
884 13 : char *c = (char *)strchr(osSubFileName.c_str(), ' ');
885 13 : if (c)
886 13 : *c = 0;
887 13 : if (EQUAL(CPLGetExtensionSafe(osSubFileName.c_str()).c_str(),
888 : "GEN"))
889 : {
890 3 : CPLDebug("ADRG", "Found GEN file in THF : %s",
891 : osSubFileName.c_str());
892 3 : CPLString osGENFileName(CPLGetDirnameSafe(pszFileName));
893 : char **tokens =
894 3 : CSLTokenizeString2(osSubFileName.c_str(), "/\"", 0);
895 3 : char **ptr = tokens;
896 3 : if (ptr == nullptr)
897 0 : continue;
898 6 : while (*ptr)
899 : {
900 : char **papszDirContent =
901 3 : VSIReadDir(osGENFileName.c_str());
902 3 : char **ptrDir = papszDirContent;
903 3 : if (ptrDir)
904 : {
905 12 : while (*ptrDir)
906 : {
907 12 : if (EQUAL(*ptrDir, *ptr))
908 : {
909 3 : osGENFileName = CPLFormFilenameSafe(
910 : osGENFileName.c_str(), *ptrDir,
911 3 : nullptr);
912 3 : CPLDebug("ADRG",
913 : "Building GEN full file name : %s",
914 : osGENFileName.c_str());
915 3 : break;
916 : }
917 9 : ptrDir++;
918 : }
919 : }
920 3 : if (ptrDir == nullptr)
921 0 : break;
922 3 : CSLDestroy(papszDirContent);
923 3 : ptr++;
924 : }
925 3 : int isNameValid = *ptr == nullptr;
926 3 : CSLDestroy(tokens);
927 3 : if (isNameValid)
928 : {
929 6 : papszFileNames = (char **)CPLRealloc(
930 3 : papszFileNames, sizeof(char *) * (nFilenames + 2));
931 6 : papszFileNames[nFilenames] =
932 3 : CPLStrdup(osGENFileName.c_str());
933 3 : papszFileNames[nFilenames + 1] = nullptr;
934 3 : nFilenames++;
935 : }
936 : }
937 : }
938 : }
939 20 : }
940 7 : return papszFileNames;
941 : }
942 :
943 : /************************************************************************/
944 : /* GetIMGListFromGEN() */
945 : /************************************************************************/
946 :
947 7 : char **ADRGDataset::GetIMGListFromGEN(const char *pszFileName,
948 : int *pnRecordIndex)
949 : {
950 7 : DDFRecord *record = nullptr;
951 7 : int nFilenames = 0;
952 7 : char **papszFileNames = nullptr;
953 7 : int nRecordIndex = -1;
954 :
955 7 : if (pnRecordIndex)
956 7 : *pnRecordIndex = -1;
957 :
958 14 : DDFModule module;
959 7 : if (!module.Open(pszFileName, TRUE))
960 0 : return nullptr;
961 :
962 : while (true)
963 : {
964 29 : nRecordIndex++;
965 :
966 29 : CPLPushErrorHandler(CPLQuietErrorHandler);
967 29 : record = module.ReadRecord();
968 29 : CPLPopErrorHandler();
969 29 : CPLErrorReset();
970 29 : if (record == nullptr)
971 7 : break;
972 :
973 22 : if (record->GetFieldCount() >= 5)
974 : {
975 15 : DDFField *field = record->GetField(0);
976 15 : DDFFieldDefn *fieldDefn = field->GetFieldDefn();
977 30 : if (!(strcmp(fieldDefn->GetName(), "001") == 0 &&
978 15 : fieldDefn->GetSubfieldCount() == 2))
979 : {
980 7 : continue;
981 : }
982 :
983 15 : const char *RTY = record->GetStringSubfield("001", 0, "RTY", 0);
984 15 : if (RTY == nullptr)
985 0 : continue;
986 : /* Ignore overviews */
987 15 : if (strcmp(RTY, "OVV") == 0)
988 7 : continue;
989 :
990 : // TODO: Fix the non-GIN section or remove it.
991 8 : if (strcmp(RTY, "GIN") != 0)
992 0 : continue;
993 :
994 : /* make sure that the GEN file is part of an ADRG dataset, not a SRP
995 : * dataset, by checking that the GEN field contains a NWO subfield
996 : */
997 8 : const char *NWO = record->GetStringSubfield("GEN", 0, "NWO", 0);
998 8 : if (NWO == nullptr)
999 : {
1000 0 : CSLDestroy(papszFileNames);
1001 0 : return nullptr;
1002 : }
1003 :
1004 8 : field = record->GetField(3);
1005 8 : if (field == nullptr)
1006 0 : continue;
1007 8 : fieldDefn = field->GetFieldDefn();
1008 :
1009 16 : if (!(strcmp(fieldDefn->GetName(), "SPR") == 0 &&
1010 8 : fieldDefn->GetSubfieldCount() == 15))
1011 : {
1012 0 : continue;
1013 : }
1014 :
1015 8 : const char *pszBAD = record->GetStringSubfield("SPR", 0, "BAD", 0);
1016 8 : if (pszBAD == nullptr || strlen(pszBAD) != 12)
1017 0 : continue;
1018 16 : std::string osBAD = pszBAD;
1019 : {
1020 8 : char *c = (char *)strchr(osBAD.c_str(), ' ');
1021 8 : if (c)
1022 0 : *c = 0;
1023 : }
1024 8 : CPLDebug("ADRG", "BAD=%s", osBAD.c_str());
1025 :
1026 : /* Build full IMG file name from BAD value */
1027 16 : CPLString osGENDir(CPLGetDirnameSafe(pszFileName));
1028 :
1029 : std::string osFileName =
1030 8 : CPLFormFilenameSafe(osGENDir.c_str(), osBAD.c_str(), nullptr);
1031 : VSIStatBufL sStatBuf;
1032 8 : if (VSIStatL(osFileName.c_str(), &sStatBuf) == 0)
1033 : {
1034 8 : osBAD = std::move(osFileName);
1035 8 : CPLDebug("ADRG", "Building IMG full file name : %s",
1036 : osBAD.c_str());
1037 : }
1038 : else
1039 : {
1040 0 : char **papszDirContent = nullptr;
1041 0 : if (strcmp(osGENDir.c_str(), "/vsimem") == 0)
1042 : {
1043 0 : CPLString osTmp = osGENDir + "/";
1044 0 : papszDirContent = VSIReadDir(osTmp);
1045 : }
1046 : else
1047 0 : papszDirContent = VSIReadDir(osGENDir);
1048 0 : char **ptrDir = papszDirContent;
1049 0 : while (ptrDir && *ptrDir)
1050 : {
1051 0 : if (EQUAL(*ptrDir, osBAD.c_str()))
1052 : {
1053 0 : osBAD = CPLFormFilenameSafe(osGENDir.c_str(), *ptrDir,
1054 0 : nullptr);
1055 0 : CPLDebug("ADRG", "Building IMG full file name : %s",
1056 : osBAD.c_str());
1057 0 : break;
1058 : }
1059 0 : ptrDir++;
1060 : }
1061 0 : CSLDestroy(papszDirContent);
1062 : }
1063 :
1064 8 : if (nFilenames == 0 && pnRecordIndex)
1065 7 : *pnRecordIndex = nRecordIndex;
1066 :
1067 16 : papszFileNames = (char **)CPLRealloc(
1068 8 : papszFileNames, sizeof(char *) * (nFilenames + 2));
1069 8 : papszFileNames[nFilenames] = CPLStrdup(osBAD.c_str());
1070 8 : papszFileNames[nFilenames + 1] = nullptr;
1071 8 : nFilenames++;
1072 : }
1073 22 : }
1074 :
1075 7 : return papszFileNames;
1076 : }
1077 :
1078 : /************************************************************************/
1079 : /* Open() */
1080 : /************************************************************************/
1081 :
1082 32862 : GDALDataset *ADRGDataset::Open(GDALOpenInfo *poOpenInfo)
1083 : {
1084 32862 : int nRecordIndex = -1;
1085 65724 : CPLString osGENFileName;
1086 65722 : CPLString osIMGFileName;
1087 32862 : bool bFromSubdataset = false;
1088 :
1089 32862 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "ADRG:"))
1090 : {
1091 : char **papszTokens =
1092 2 : CSLTokenizeString2(poOpenInfo->pszFilename + 5, ",", 0);
1093 2 : if (CSLCount(papszTokens) == 2)
1094 : {
1095 2 : osGENFileName = papszTokens[0];
1096 2 : osIMGFileName = papszTokens[1];
1097 2 : bFromSubdataset = true;
1098 : }
1099 2 : CSLDestroy(papszTokens);
1100 : }
1101 : else
1102 : {
1103 32860 : if (poOpenInfo->nHeaderBytes < 500)
1104 30768 : return nullptr;
1105 :
1106 2097 : CPLString osFileName(poOpenInfo->pszFilename);
1107 2097 : if (EQUAL(CPLGetExtensionSafe(osFileName.c_str()).c_str(), "THF"))
1108 : {
1109 7 : char **papszFileNames = GetGENListFromTHF(osFileName.c_str());
1110 7 : if (papszFileNames == nullptr)
1111 4 : return nullptr;
1112 3 : if (papszFileNames[1] == nullptr)
1113 : {
1114 3 : osFileName = papszFileNames[0];
1115 3 : CSLDestroy(papszFileNames);
1116 : }
1117 : else
1118 : {
1119 0 : char **ptr = papszFileNames;
1120 0 : ADRGDataset *poDS = new ADRGDataset();
1121 0 : while (*ptr)
1122 : {
1123 0 : char **papszIMGFileNames = GetIMGListFromGEN(*ptr);
1124 0 : char **papszIMGIter = papszIMGFileNames;
1125 0 : while (papszIMGIter && *papszIMGIter)
1126 : {
1127 0 : poDS->AddSubDataset(*ptr, *papszIMGIter);
1128 0 : papszIMGIter++;
1129 : }
1130 0 : CSLDestroy(papszIMGFileNames);
1131 :
1132 0 : ptr++;
1133 : }
1134 0 : CSLDestroy(papszFileNames);
1135 0 : return poDS;
1136 : }
1137 : }
1138 :
1139 2093 : if (EQUAL(CPLGetExtensionSafe(osFileName.c_str()).c_str(), "GEN"))
1140 : {
1141 7 : osGENFileName = osFileName;
1142 :
1143 : char **papszFileNames =
1144 7 : GetIMGListFromGEN(osFileName.c_str(), &nRecordIndex);
1145 7 : if (papszFileNames == nullptr)
1146 0 : return nullptr;
1147 7 : if (papszFileNames[1] == nullptr)
1148 : {
1149 6 : osIMGFileName = papszFileNames[0];
1150 6 : CSLDestroy(papszFileNames);
1151 : }
1152 : else
1153 : {
1154 1 : char **ptr = papszFileNames;
1155 1 : ADRGDataset *poDS = new ADRGDataset();
1156 3 : while (*ptr)
1157 : {
1158 2 : poDS->AddSubDataset(osFileName.c_str(), *ptr);
1159 2 : ptr++;
1160 : }
1161 1 : CSLDestroy(papszFileNames);
1162 1 : return poDS;
1163 : }
1164 : }
1165 : }
1166 :
1167 2094 : if (!osGENFileName.empty() && !osIMGFileName.empty())
1168 : {
1169 8 : if (poOpenInfo->eAccess == GA_Update)
1170 : {
1171 0 : ReportUpdateNotSupportedByDriver("ADRG");
1172 8 : return nullptr;
1173 : }
1174 :
1175 8 : DDFModule module;
1176 8 : DDFRecord *record = nullptr;
1177 8 : if (nRecordIndex >= 0 && module.Open(osGENFileName.c_str(), TRUE))
1178 : {
1179 24 : for (int i = 0; i <= nRecordIndex; i++)
1180 : {
1181 18 : CPLPushErrorHandler(CPLQuietErrorHandler);
1182 18 : record = module.ReadRecord();
1183 18 : CPLPopErrorHandler();
1184 18 : CPLErrorReset();
1185 18 : if (record == nullptr)
1186 0 : break;
1187 : }
1188 : }
1189 :
1190 : ADRGDataset *poDS =
1191 8 : OpenDataset(osGENFileName.c_str(), osIMGFileName.c_str(), record);
1192 :
1193 8 : if (poDS)
1194 : {
1195 : /* -------------------------------------------------------------- */
1196 : /* Initialize any PAM information. */
1197 : /* -------------------------------------------------------------- */
1198 8 : poDS->SetDescription(poOpenInfo->pszFilename);
1199 8 : poDS->TryLoadXML();
1200 :
1201 : /* -------------------------------------------------------------- */
1202 : /* Check for external overviews. */
1203 : /* -------------------------------------------------------------- */
1204 8 : if (bFromSubdataset)
1205 2 : poDS->oOvManager.Initialize(poDS, osIMGFileName.c_str());
1206 : else
1207 6 : poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
1208 :
1209 8 : return poDS;
1210 : }
1211 : }
1212 :
1213 2086 : return nullptr;
1214 : }
1215 :
1216 : /************************************************************************/
1217 : /* GDALRegister_ADRG() */
1218 : /************************************************************************/
1219 :
1220 1935 : void GDALRegister_ADRG()
1221 :
1222 : {
1223 1935 : if (GDALGetDriverByName("ADRG") != nullptr)
1224 282 : return;
1225 :
1226 1653 : GDALDriver *poDriver = new GDALDriver();
1227 :
1228 1653 : poDriver->SetDescription("ADRG");
1229 1653 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1230 1653 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
1231 1653 : "ARC Digitized Raster Graphics");
1232 1653 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/adrg.html");
1233 1653 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "gen");
1234 1653 : poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
1235 1653 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1236 :
1237 1653 : poDriver->pfnOpen = ADRGDataset::Open;
1238 :
1239 1653 : GetGDALDriverManager()->RegisterDriver(poDriver);
1240 : }
|