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, GDAL_MDD_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, GDAL_MDD_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 : const 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 : const 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->GetSubfields()[14].get();
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", pszIMGFileName);
677 0 : delete[] TILEINDEX;
678 0 : return nullptr;
679 : }
680 :
681 : /* Skip ISO8211 header of IMG file */
682 8 : int offsetInIMG = 0;
683 : char c;
684 : char recordName[3];
685 8 : if (VSIFReadL(&c, 1, 1, fdIMG) != 1)
686 : {
687 0 : VSIFCloseL(fdIMG);
688 0 : delete[] TILEINDEX;
689 0 : return nullptr;
690 : }
691 2064 : while (!VSIFEofL(fdIMG))
692 : {
693 2064 : if (c == 30)
694 : {
695 48 : if (VSIFReadL(recordName, 1, 3, fdIMG) != 3)
696 : {
697 0 : VSIFCloseL(fdIMG);
698 0 : delete[] TILEINDEX;
699 0 : return nullptr;
700 : }
701 48 : offsetInIMG += 3;
702 48 : if (STARTS_WITH(recordName, "IMG"))
703 : {
704 8 : offsetInIMG += 4;
705 16 : if (VSIFSeekL(fdIMG, 3, SEEK_CUR) != 0 ||
706 8 : VSIFReadL(&c, 1, 1, fdIMG) != 1)
707 : {
708 0 : VSIFCloseL(fdIMG);
709 0 : delete[] TILEINDEX;
710 0 : return nullptr;
711 : }
712 14152 : while (c == ' ')
713 : {
714 14144 : offsetInIMG++;
715 14144 : if (VSIFReadL(&c, 1, 1, fdIMG) != 1)
716 : {
717 0 : VSIFCloseL(fdIMG);
718 0 : delete[] TILEINDEX;
719 0 : return nullptr;
720 : }
721 : }
722 8 : offsetInIMG++;
723 8 : break;
724 : }
725 : }
726 :
727 2056 : offsetInIMG++;
728 2056 : if (VSIFReadL(&c, 1, 1, fdIMG) != 1)
729 : {
730 0 : VSIFCloseL(fdIMG);
731 0 : delete[] TILEINDEX;
732 0 : return nullptr;
733 : }
734 : }
735 :
736 8 : if (VSIFEofL(fdIMG))
737 : {
738 0 : VSIFCloseL(fdIMG);
739 0 : delete[] TILEINDEX;
740 0 : return nullptr;
741 : }
742 :
743 8 : CPLDebug("ADRG", "Img offset data = %d", offsetInIMG);
744 :
745 8 : ADRGDataset *poDS = new ADRGDataset();
746 :
747 8 : poDS->osGENFileName = pszGENFileName;
748 8 : poDS->osIMGFileName = pszIMGFileName;
749 8 : poDS->NFC = NFC;
750 8 : poDS->NFL = NFL;
751 8 : poDS->nRasterXSize = NFC * 128;
752 8 : poDS->nRasterYSize = NFL * 128;
753 8 : poDS->LSO = LSO;
754 8 : poDS->PSO = PSO;
755 8 : poDS->ARV = ARV;
756 8 : poDS->BRV = BRV;
757 8 : poDS->TILEINDEX = TILEINDEX;
758 8 : poDS->fdIMG = fdIMG;
759 8 : poDS->offsetInIMG = offsetInIMG;
760 :
761 8 : if (ZNA == 9)
762 : {
763 : // North Polar Case
764 1 : poDS->m_gt.xorig =
765 1 : 111319.4907933 * (90.0 - PSO) * sin(LSO * M_PI / 180.0);
766 1 : poDS->m_gt.xscale = 40075016.68558 / ARV;
767 1 : poDS->m_gt.xrot = 0.0;
768 1 : poDS->m_gt.yorig =
769 1 : -111319.4907933 * (90.0 - PSO) * cos(LSO * M_PI / 180.0);
770 1 : poDS->m_gt.yrot = 0.0;
771 1 : poDS->m_gt.yscale = -40075016.68558 / ARV;
772 1 : poDS->m_oSRS.importFromWkt(
773 : "PROJCS[\"ARC_System_Zone_09\",GEOGCS[\"GCS_Sphere\","
774 : "DATUM[\"D_Sphere\",SPHEROID[\"Sphere\",6378137.0,0.0]],"
775 : "PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]],"
776 : "PROJECTION[\"Azimuthal_Equidistant\"],"
777 : "PARAMETER[\"latitude_of_center\",90],"
778 : "PARAMETER[\"longitude_of_center\",0],"
779 : "PARAMETER[\"false_easting\",0],"
780 : "PARAMETER[\"false_northing\",0],"
781 : "UNIT[\"metre\",1]]");
782 : }
783 7 : else if (ZNA == 18)
784 : {
785 : // South Polar Case
786 1 : poDS->m_gt.xorig =
787 1 : 111319.4907933 * (90.0 + PSO) * sin(LSO * M_PI / 180.0);
788 1 : poDS->m_gt.xscale = 40075016.68558 / ARV;
789 1 : poDS->m_gt.xrot = 0.0;
790 1 : poDS->m_gt.yorig =
791 1 : 111319.4907933 * (90.0 + PSO) * cos(LSO * M_PI / 180.0);
792 1 : poDS->m_gt.yrot = 0.0;
793 1 : poDS->m_gt.yscale = -40075016.68558 / ARV;
794 1 : poDS->m_oSRS.importFromWkt(
795 : "PROJCS[\"ARC_System_Zone_18\",GEOGCS[\"GCS_Sphere\","
796 : "DATUM[\"D_Sphere\",SPHEROID[\"Sphere\",6378137.0,0.0]],"
797 : "PRIMEM[\"Greenwich\",0],UNIT[\"degree\",0.0174532925199433]],"
798 : "PROJECTION[\"Azimuthal_Equidistant\"],"
799 : "PARAMETER[\"latitude_of_center\",-90],"
800 : "PARAMETER[\"longitude_of_center\",0],"
801 : "PARAMETER[\"false_easting\",0],"
802 : "PARAMETER[\"false_northing\",0],"
803 : "UNIT[\"metre\",1]]");
804 : }
805 : else
806 : {
807 6 : poDS->m_gt.xorig = LSO;
808 6 : poDS->m_gt.xscale = 360. / ARV;
809 6 : poDS->m_gt.xrot = 0.0;
810 6 : poDS->m_gt.yorig = PSO;
811 6 : poDS->m_gt.yrot = 0.0;
812 6 : poDS->m_gt.yscale = -360. / BRV;
813 6 : poDS->m_oSRS.importFromWkt(SRS_WKT_WGS84_LAT_LONG);
814 : }
815 :
816 : // if( isGIN )
817 : {
818 : char szValue[32];
819 8 : snprintf(szValue, sizeof(szValue), "%d", SCA);
820 8 : poDS->SetMetadataItem("ADRG_SCA", szValue);
821 8 : snprintf(szValue, sizeof(szValue), "%d", ZNA);
822 8 : poDS->SetMetadataItem("ADRG_ZNA", szValue);
823 : }
824 :
825 8 : poDS->SetMetadataItem("ADRG_NAM", osNAM.c_str());
826 :
827 8 : poDS->nBands = 3;
828 32 : for (int i = 0; i < poDS->nBands; i++)
829 24 : poDS->SetBand(i + 1, new ADRGRasterBand(poDS, i + 1));
830 :
831 8 : return poDS;
832 : }
833 :
834 : /************************************************************************/
835 : /* GetGENListFromTHF() */
836 : /************************************************************************/
837 :
838 7 : char **ADRGDataset::GetGENListFromTHF(const char *pszFileName)
839 : {
840 14 : DDFModule module;
841 7 : DDFRecord *record = nullptr;
842 7 : int nFilenames = 0;
843 7 : char **papszFileNames = nullptr;
844 :
845 7 : if (!module.Open(pszFileName, TRUE))
846 0 : return papszFileNames;
847 :
848 : while (true)
849 : {
850 27 : CPLPushErrorHandler(CPLQuietErrorHandler);
851 27 : record = module.ReadRecord();
852 27 : CPLPopErrorHandler();
853 27 : CPLErrorReset();
854 27 : if (record == nullptr)
855 7 : break;
856 :
857 20 : if (record->GetFieldCount() >= 2)
858 : {
859 20 : DDFField *field = record->GetField(0);
860 20 : const DDFFieldDefn *fieldDefn = field->GetFieldDefn();
861 40 : if (!(strcmp(fieldDefn->GetName(), "001") == 0 &&
862 20 : fieldDefn->GetSubfieldCount() == 2))
863 : {
864 0 : continue;
865 : }
866 :
867 20 : const char *RTY = record->GetStringSubfield("001", 0, "RTY", 0);
868 20 : if (RTY == nullptr || !(strcmp(RTY, "TFN") == 0))
869 : {
870 17 : continue;
871 : }
872 :
873 3 : int iVFFFieldInstance = 0;
874 16 : for (int i = 1; i < record->GetFieldCount(); i++)
875 : {
876 13 : field = record->GetField(i);
877 13 : fieldDefn = field->GetFieldDefn();
878 :
879 26 : if (!(strcmp(fieldDefn->GetName(), "VFF") == 0 &&
880 13 : fieldDefn->GetSubfieldCount() == 1))
881 : {
882 0 : continue;
883 : }
884 :
885 13 : const char *pszVFF = record->GetStringSubfield(
886 : "VFF", iVFFFieldInstance++, "VFF", 0);
887 13 : if (pszVFF == nullptr)
888 0 : continue;
889 13 : CPLString osSubFileName(pszVFF);
890 13 : char *c = (char *)strchr(osSubFileName.c_str(), ' ');
891 13 : if (c)
892 13 : *c = 0;
893 13 : if (EQUAL(CPLGetExtensionSafe(osSubFileName.c_str()).c_str(),
894 : "GEN"))
895 : {
896 3 : CPLDebug("ADRG", "Found GEN file in THF : %s",
897 : osSubFileName.c_str());
898 3 : CPLString osGENFileName(CPLGetDirnameSafe(pszFileName));
899 : char **tokens =
900 3 : CSLTokenizeString2(osSubFileName.c_str(), "/\"", 0);
901 3 : char **ptr = tokens;
902 3 : if (ptr == nullptr)
903 0 : continue;
904 6 : while (*ptr)
905 : {
906 : char **papszDirContent =
907 3 : VSIReadDir(osGENFileName.c_str());
908 3 : char **ptrDir = papszDirContent;
909 3 : if (ptrDir)
910 : {
911 5 : while (*ptrDir)
912 : {
913 5 : if (EQUAL(*ptrDir, *ptr))
914 : {
915 3 : osGENFileName = CPLFormFilenameSafe(
916 : osGENFileName.c_str(), *ptrDir,
917 3 : nullptr);
918 3 : CPLDebug("ADRG",
919 : "Building GEN full file name : %s",
920 : osGENFileName.c_str());
921 3 : break;
922 : }
923 2 : ptrDir++;
924 : }
925 : }
926 3 : if (ptrDir == nullptr)
927 0 : break;
928 3 : CSLDestroy(papszDirContent);
929 3 : ptr++;
930 : }
931 3 : int isNameValid = *ptr == nullptr;
932 3 : CSLDestroy(tokens);
933 3 : if (isNameValid)
934 : {
935 6 : papszFileNames = (char **)CPLRealloc(
936 3 : papszFileNames, sizeof(char *) * (nFilenames + 2));
937 6 : papszFileNames[nFilenames] =
938 3 : CPLStrdup(osGENFileName.c_str());
939 3 : papszFileNames[nFilenames + 1] = nullptr;
940 3 : nFilenames++;
941 : }
942 : }
943 : }
944 : }
945 20 : }
946 7 : return papszFileNames;
947 : }
948 :
949 : /************************************************************************/
950 : /* GetIMGListFromGEN() */
951 : /************************************************************************/
952 :
953 7 : char **ADRGDataset::GetIMGListFromGEN(const char *pszFileName,
954 : int *pnRecordIndex)
955 : {
956 7 : DDFRecord *record = nullptr;
957 14 : CPLStringList aosFilenames;
958 7 : int nRecordIndex = -1;
959 :
960 7 : if (pnRecordIndex)
961 7 : *pnRecordIndex = -1;
962 :
963 14 : DDFModule module;
964 7 : if (!module.Open(pszFileName, TRUE))
965 0 : return nullptr;
966 :
967 : while (true)
968 : {
969 29 : nRecordIndex++;
970 :
971 29 : CPLPushErrorHandler(CPLQuietErrorHandler);
972 29 : record = module.ReadRecord();
973 29 : CPLPopErrorHandler();
974 29 : CPLErrorReset();
975 29 : if (record == nullptr)
976 7 : break;
977 :
978 22 : if (record->GetFieldCount() >= 5)
979 : {
980 15 : DDFField *field = record->GetField(0);
981 15 : const DDFFieldDefn *fieldDefn = field->GetFieldDefn();
982 30 : if (!(strcmp(fieldDefn->GetName(), "001") == 0 &&
983 15 : fieldDefn->GetSubfieldCount() == 2))
984 : {
985 7 : continue;
986 : }
987 :
988 15 : const char *RTY = record->GetStringSubfield("001", 0, "RTY", 0);
989 15 : if (RTY == nullptr)
990 0 : continue;
991 : /* Ignore overviews */
992 15 : if (strcmp(RTY, "OVV") == 0)
993 7 : continue;
994 :
995 : // TODO: Fix the non-GIN section or remove it.
996 8 : if (strcmp(RTY, "GIN") != 0)
997 0 : continue;
998 :
999 : /* make sure that the GEN file is part of an ADRG dataset, not a SRP
1000 : * dataset, by checking that the GEN field contains a NWO subfield
1001 : */
1002 8 : const char *NWO = record->GetStringSubfield("GEN", 0, "NWO", 0);
1003 8 : if (NWO == nullptr)
1004 : {
1005 0 : return nullptr;
1006 : }
1007 :
1008 8 : field = record->GetField(3);
1009 8 : if (field == nullptr)
1010 0 : continue;
1011 8 : fieldDefn = field->GetFieldDefn();
1012 :
1013 16 : if (!(strcmp(fieldDefn->GetName(), "SPR") == 0 &&
1014 8 : fieldDefn->GetSubfieldCount() == 15))
1015 : {
1016 0 : continue;
1017 : }
1018 :
1019 8 : const char *pszBAD = record->GetStringSubfield("SPR", 0, "BAD", 0);
1020 8 : if (pszBAD == nullptr || strlen(pszBAD) != 12)
1021 0 : continue;
1022 8 : std::string osBAD = pszBAD;
1023 8 : const auto nSpacePos = osBAD.find(' ');
1024 8 : if (nSpacePos != std::string::npos)
1025 0 : osBAD.resize(nSpacePos);
1026 8 : CPLDebug("ADRG", "BAD=%s", osBAD.c_str());
1027 8 : if (CPLHasPathTraversal(osBAD.c_str()))
1028 : {
1029 0 : CPLError(CE_Failure, CPLE_AppDefined,
1030 : "Path traversal detected in %s", osBAD.c_str());
1031 0 : return nullptr;
1032 : }
1033 :
1034 : /* Build full IMG file name from BAD value */
1035 16 : CPLString osGENDir(CPLGetDirnameSafe(pszFileName));
1036 :
1037 : std::string osFileName =
1038 16 : CPLFormFilenameSafe(osGENDir.c_str(), osBAD.c_str(), nullptr);
1039 : VSIStatBufL sStatBuf;
1040 8 : if (VSIStatL(osFileName.c_str(), &sStatBuf) == 0)
1041 : {
1042 8 : osBAD = std::move(osFileName);
1043 8 : CPLDebug("ADRG", "Building IMG full file name : %s",
1044 : osBAD.c_str());
1045 : }
1046 : else
1047 : {
1048 0 : char **papszDirContent = nullptr;
1049 0 : if (strcmp(osGENDir.c_str(), "/vsimem") == 0)
1050 : {
1051 0 : CPLString osTmp = osGENDir + "/";
1052 0 : papszDirContent = VSIReadDir(osTmp);
1053 : }
1054 : else
1055 0 : papszDirContent = VSIReadDir(osGENDir);
1056 0 : char **ptrDir = papszDirContent;
1057 0 : while (ptrDir && *ptrDir)
1058 : {
1059 0 : if (EQUAL(*ptrDir, osBAD.c_str()))
1060 : {
1061 0 : osBAD = CPLFormFilenameSafe(osGENDir.c_str(), *ptrDir,
1062 0 : nullptr);
1063 0 : CPLDebug("ADRG", "Building IMG full file name : %s",
1064 : osBAD.c_str());
1065 0 : break;
1066 : }
1067 0 : ptrDir++;
1068 : }
1069 0 : CSLDestroy(papszDirContent);
1070 : }
1071 :
1072 8 : if (aosFilenames.empty() && pnRecordIndex)
1073 7 : *pnRecordIndex = nRecordIndex;
1074 :
1075 8 : aosFilenames.AddString(osBAD.c_str());
1076 : }
1077 22 : }
1078 :
1079 7 : return aosFilenames.StealList();
1080 : }
1081 :
1082 : /************************************************************************/
1083 : /* Open() */
1084 : /************************************************************************/
1085 :
1086 36649 : GDALDataset *ADRGDataset::Open(GDALOpenInfo *poOpenInfo)
1087 : {
1088 36649 : int nRecordIndex = -1;
1089 73298 : CPLString osGENFileName;
1090 73298 : CPLString osIMGFileName;
1091 36649 : bool bFromSubdataset = false;
1092 :
1093 36649 : if (STARTS_WITH_CI(poOpenInfo->pszFilename, "ADRG:"))
1094 : {
1095 : char **papszTokens =
1096 2 : CSLTokenizeString2(poOpenInfo->pszFilename + 5, ",", 0);
1097 2 : if (CSLCount(papszTokens) == 2)
1098 : {
1099 2 : osGENFileName = papszTokens[0];
1100 2 : osIMGFileName = papszTokens[1];
1101 2 : bFromSubdataset = true;
1102 : }
1103 2 : CSLDestroy(papszTokens);
1104 : }
1105 : else
1106 : {
1107 36647 : if (poOpenInfo->nHeaderBytes < 500)
1108 32920 : return nullptr;
1109 :
1110 3732 : CPLString osFileName(poOpenInfo->pszFilename);
1111 3732 : if (EQUAL(CPLGetExtensionSafe(osFileName.c_str()).c_str(), "THF"))
1112 : {
1113 7 : char **papszFileNames = GetGENListFromTHF(osFileName.c_str());
1114 7 : if (papszFileNames == nullptr)
1115 4 : return nullptr;
1116 3 : if (papszFileNames[1] == nullptr)
1117 : {
1118 3 : osFileName = papszFileNames[0];
1119 3 : CSLDestroy(papszFileNames);
1120 : }
1121 : else
1122 : {
1123 0 : char **ptr = papszFileNames;
1124 0 : ADRGDataset *poDS = new ADRGDataset();
1125 0 : while (*ptr)
1126 : {
1127 0 : char **papszIMGFileNames = GetIMGListFromGEN(*ptr);
1128 0 : char **papszIMGIter = papszIMGFileNames;
1129 0 : while (papszIMGIter && *papszIMGIter)
1130 : {
1131 0 : poDS->AddSubDataset(*ptr, *papszIMGIter);
1132 0 : papszIMGIter++;
1133 : }
1134 0 : CSLDestroy(papszIMGFileNames);
1135 :
1136 0 : ptr++;
1137 : }
1138 0 : CSLDestroy(papszFileNames);
1139 0 : return poDS;
1140 : }
1141 : }
1142 :
1143 3728 : if (EQUAL(CPLGetExtensionSafe(osFileName.c_str()).c_str(), "GEN"))
1144 : {
1145 7 : osGENFileName = osFileName;
1146 :
1147 : char **papszFileNames =
1148 7 : GetIMGListFromGEN(osFileName.c_str(), &nRecordIndex);
1149 7 : if (papszFileNames == nullptr)
1150 0 : return nullptr;
1151 7 : if (papszFileNames[1] == nullptr)
1152 : {
1153 6 : osIMGFileName = papszFileNames[0];
1154 6 : CSLDestroy(papszFileNames);
1155 : }
1156 : else
1157 : {
1158 1 : char **ptr = papszFileNames;
1159 1 : ADRGDataset *poDS = new ADRGDataset();
1160 3 : while (*ptr)
1161 : {
1162 2 : poDS->AddSubDataset(osFileName.c_str(), *ptr);
1163 2 : ptr++;
1164 : }
1165 1 : CSLDestroy(papszFileNames);
1166 1 : return poDS;
1167 : }
1168 : }
1169 : }
1170 :
1171 3729 : if (!osGENFileName.empty() && !osIMGFileName.empty())
1172 : {
1173 8 : if (poOpenInfo->eAccess == GA_Update)
1174 : {
1175 0 : ReportUpdateNotSupportedByDriver("ADRG");
1176 8 : return nullptr;
1177 : }
1178 :
1179 8 : DDFModule module;
1180 8 : DDFRecord *record = nullptr;
1181 8 : if (nRecordIndex >= 0 && module.Open(osGENFileName.c_str(), TRUE))
1182 : {
1183 24 : for (int i = 0; i <= nRecordIndex; i++)
1184 : {
1185 18 : CPLPushErrorHandler(CPLQuietErrorHandler);
1186 18 : record = module.ReadRecord();
1187 18 : CPLPopErrorHandler();
1188 18 : CPLErrorReset();
1189 18 : if (record == nullptr)
1190 0 : break;
1191 : }
1192 : }
1193 :
1194 : ADRGDataset *poDS =
1195 8 : OpenDataset(osGENFileName.c_str(), osIMGFileName.c_str(), record);
1196 :
1197 8 : if (poDS)
1198 : {
1199 : /* -------------------------------------------------------------- */
1200 : /* Initialize any PAM information. */
1201 : /* -------------------------------------------------------------- */
1202 8 : poDS->SetDescription(poOpenInfo->pszFilename);
1203 8 : poDS->TryLoadXML();
1204 :
1205 : /* -------------------------------------------------------------- */
1206 : /* Check for external overviews. */
1207 : /* -------------------------------------------------------------- */
1208 8 : if (bFromSubdataset)
1209 2 : poDS->oOvManager.Initialize(poDS, osIMGFileName.c_str());
1210 : else
1211 6 : poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename);
1212 :
1213 8 : return poDS;
1214 : }
1215 : }
1216 :
1217 3721 : return nullptr;
1218 : }
1219 :
1220 : /************************************************************************/
1221 : /* GDALRegister_ADRG() */
1222 : /************************************************************************/
1223 :
1224 2062 : void GDALRegister_ADRG()
1225 :
1226 : {
1227 2062 : if (GDALGetDriverByName("ADRG") != nullptr)
1228 263 : return;
1229 :
1230 1799 : GDALDriver *poDriver = new GDALDriver();
1231 :
1232 1799 : poDriver->SetDescription("ADRG");
1233 1799 : poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1234 1799 : poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
1235 1799 : "ARC Digitized Raster Graphics");
1236 1799 : poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/adrg.html");
1237 1799 : poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "gen");
1238 1799 : poDriver->SetMetadataItem(GDAL_DMD_SUBDATASETS, "YES");
1239 1799 : poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1240 :
1241 1799 : poDriver->pfnOpen = ADRGDataset::Open;
1242 :
1243 1799 : GetGDALDriverManager()->RegisterDriver(poDriver);
1244 : }
|