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