Coverage Report

Created: 2026-08-11 08:26

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/aigrid/aigdataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  Arc/Info Binary Grid Driver
4
 * Purpose:  Implements GDAL interface to underlying library.
5
 * Author:   Frank Warmerdam, warmerdam@pobox.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 1999, Frank Warmerdam
9
 * Copyright (c) 2008-2013, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "aigrid.h"
15
#include "avc.h"
16
#include "cpl_string.h"
17
#include "gdal_frmts.h"
18
#include "gdal_pam.h"
19
#include "gdal_colortable.h"
20
#include "gdal_driver.h"
21
#include "gdal_drivermanager.h"
22
#include "gdal_openinfo.h"
23
#include "gdal_cpp_functions.h"
24
#include "gdal_rat.h"
25
#include "ogr_spatialref.h"
26
27
#include <vector>
28
29
static CPLString OSR_GDS(char **papszNV, const char *pszField,
30
                         const char *pszDefaultValue);
31
32
/************************************************************************/
33
/* ==================================================================== */
34
/*                              AIGDataset                              */
35
/* ==================================================================== */
36
/************************************************************************/
37
38
class AIGRasterBand;
39
40
class AIGDataset final : public GDALPamDataset
41
{
42
    friend class AIGRasterBand;
43
44
    AIGInfo_t *psInfo;
45
46
    char **papszPrj;
47
    OGRSpatialReference m_oSRS{};
48
49
    GDALColorTable *poCT;
50
    bool bHasReadRat;
51
52
    void TranslateColorTable(const char *);
53
54
    void ReadRAT();
55
    GDALRasterAttributeTable *poRAT;
56
57
  public:
58
    AIGDataset();
59
    ~AIGDataset() override;
60
61
    static GDALDataset *Open(GDALOpenInfo *);
62
63
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
64
    const OGRSpatialReference *GetSpatialRef() const override;
65
    char **GetFileList(void) override;
66
};
67
68
/************************************************************************/
69
/* ==================================================================== */
70
/*                            AIGRasterBand                             */
71
/* ==================================================================== */
72
/************************************************************************/
73
74
class AIGRasterBand final : public GDALPamRasterBand
75
76
{
77
    friend class AIGDataset;
78
79
  public:
80
    AIGRasterBand(AIGDataset *, int);
81
82
    CPLErr IReadBlock(int, int, void *) override;
83
    double GetMinimum(int *pbSuccess) override;
84
    double GetMaximum(int *pbSuccess) override;
85
    double GetNoDataValue(int *pbSuccess) override;
86
87
    GDALColorInterp GetColorInterpretation() override;
88
    GDALColorTable *GetColorTable() override;
89
    GDALRasterAttributeTable *GetDefaultRAT() override;
90
};
91
92
/************************************************************************/
93
/*                           AIGRasterBand()                            */
94
/************************************************************************/
95
96
AIGRasterBand::AIGRasterBand(AIGDataset *poDSIn, int nBandIn)
97
98
8.03k
{
99
8.03k
    poDS = poDSIn;
100
8.03k
    nBand = nBandIn;
101
102
8.03k
    nBlockXSize = poDSIn->psInfo->nBlockXSize;
103
8.03k
    nBlockYSize = poDSIn->psInfo->nBlockYSize;
104
105
8.03k
    if (poDSIn->psInfo->nCellType == AIG_CELLTYPE_INT &&
106
2.18k
        poDSIn->psInfo->dfMin >= 0.0 && poDSIn->psInfo->dfMax <= 254.0)
107
169
    {
108
169
        eDataType = GDT_UInt8;
109
169
    }
110
7.86k
    else if (poDSIn->psInfo->nCellType == AIG_CELLTYPE_INT &&
111
2.02k
             poDSIn->psInfo->dfMin >= -32767 && poDSIn->psInfo->dfMax <= 32767)
112
22
    {
113
22
        eDataType = GDT_Int16;
114
22
    }
115
7.84k
    else if (poDSIn->psInfo->nCellType == AIG_CELLTYPE_INT)
116
1.99k
    {
117
1.99k
        eDataType = GDT_Int32;
118
1.99k
    }
119
5.84k
    else
120
5.84k
    {
121
5.84k
        eDataType = GDT_Float32;
122
5.84k
    }
123
8.03k
}
124
125
/************************************************************************/
126
/*                             IReadBlock()                             */
127
/************************************************************************/
128
129
CPLErr AIGRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
130
131
675k
{
132
675k
    AIGDataset *poODS = cpl::down_cast<AIGDataset *>(poDS);
133
675k
    GInt32 *panGridRaster;
134
135
675k
    if (poODS->psInfo->nCellType == AIG_CELLTYPE_INT)
136
47.2k
    {
137
47.2k
        panGridRaster = (GInt32 *)VSIMalloc3(4, nBlockXSize, nBlockYSize);
138
47.2k
        if (panGridRaster == nullptr ||
139
47.2k
            AIGReadTile(poODS->psInfo, nBlockXOff, nBlockYOff, panGridRaster) !=
140
47.2k
                CE_None)
141
86
        {
142
86
            CPLFree(panGridRaster);
143
86
            return CE_Failure;
144
86
        }
145
146
47.1k
        if (eDataType == GDT_UInt8)
147
8.35k
        {
148
26.8M
            for (int i = 0; i < nBlockXSize * nBlockYSize; i++)
149
26.8M
            {
150
26.8M
                if (panGridRaster[i] == ESRI_GRID_NO_DATA)
151
26.8M
                    ((GByte *)pImage)[i] = 255;
152
12.8k
                else
153
12.8k
                    ((GByte *)pImage)[i] = (GByte)panGridRaster[i];
154
26.8M
            }
155
8.35k
        }
156
38.8k
        else if (eDataType == GDT_Int16)
157
834
        {
158
1.83M
            for (int i = 0; i < nBlockXSize * nBlockYSize; i++)
159
1.83M
            {
160
1.83M
                if (panGridRaster[i] == ESRI_GRID_NO_DATA)
161
1.83M
                    ((GInt16 *)pImage)[i] = -32768;
162
264
                else
163
264
                    ((GInt16 *)pImage)[i] = (GInt16)panGridRaster[i];
164
1.83M
            }
165
834
        }
166
37.9k
        else
167
37.9k
        {
168
273M
            for (int i = 0; i < nBlockXSize * nBlockYSize; i++)
169
273M
                ((GInt32 *)pImage)[i] = panGridRaster[i];
170
37.9k
        }
171
172
47.1k
        CPLFree(panGridRaster);
173
174
47.1k
        return CE_None;
175
47.2k
    }
176
627k
    else
177
627k
    {
178
627k
        return AIGReadFloatTile(poODS->psInfo, nBlockXOff, nBlockYOff,
179
627k
                                (float *)pImage);
180
627k
    }
181
675k
}
182
183
/************************************************************************/
184
/*                           GetDefaultRAT()                            */
185
/************************************************************************/
186
187
GDALRasterAttributeTable *AIGRasterBand::GetDefaultRAT()
188
189
0
{
190
0
    AIGDataset *poODS = cpl::down_cast<AIGDataset *>(poDS);
191
192
    /* -------------------------------------------------------------------- */
193
    /*      Read info raster attribute table, if present.                   */
194
    /* -------------------------------------------------------------------- */
195
0
    if (!poODS->bHasReadRat)
196
0
    {
197
0
        poODS->ReadRAT();
198
0
        poODS->bHasReadRat = true;
199
0
    }
200
201
0
    if (poODS->poRAT)
202
0
        return poODS->poRAT;
203
0
    else
204
0
        return GDALPamRasterBand::GetDefaultRAT();
205
0
}
206
207
/************************************************************************/
208
/*                             GetMinimum()                             */
209
/************************************************************************/
210
211
double AIGRasterBand::GetMinimum(int *pbSuccess)
212
213
0
{
214
0
    AIGDataset *poODS = cpl::down_cast<AIGDataset *>(poDS);
215
216
0
    if (pbSuccess != nullptr)
217
0
        *pbSuccess = TRUE;
218
219
0
    return poODS->psInfo->dfMin;
220
0
}
221
222
/************************************************************************/
223
/*                             GetMaximum()                             */
224
/************************************************************************/
225
226
double AIGRasterBand::GetMaximum(int *pbSuccess)
227
228
0
{
229
0
    AIGDataset *poODS = cpl::down_cast<AIGDataset *>(poDS);
230
231
0
    if (pbSuccess != nullptr)
232
0
        *pbSuccess = TRUE;
233
234
0
    return poODS->psInfo->dfMax;
235
0
}
236
237
/************************************************************************/
238
/*                           GetNoDataValue()                           */
239
/************************************************************************/
240
241
double AIGRasterBand::GetNoDataValue(int *pbSuccess)
242
243
32.1k
{
244
32.1k
    if (pbSuccess != nullptr)
245
24.1k
        *pbSuccess = TRUE;
246
247
32.1k
    if (eDataType == GDT_Float32)
248
23.3k
        return ESRI_GRID_FLOAT_NO_DATA;
249
250
8.75k
    if (eDataType == GDT_Int16)
251
88
        return -32768;
252
253
8.66k
    if (eDataType == GDT_UInt8)
254
676
        return 255;
255
256
7.99k
    return ESRI_GRID_NO_DATA;
257
8.66k
}
258
259
/************************************************************************/
260
/*                       GetColorInterpretation()                       */
261
/************************************************************************/
262
263
GDALColorInterp AIGRasterBand::GetColorInterpretation()
264
265
0
{
266
0
    AIGDataset *poODS = cpl::down_cast<AIGDataset *>(poDS);
267
268
0
    if (poODS->poCT != nullptr)
269
0
        return GCI_PaletteIndex;
270
271
0
    return GDALPamRasterBand::GetColorInterpretation();
272
0
}
273
274
/************************************************************************/
275
/*                           GetColorTable()                            */
276
/************************************************************************/
277
278
GDALColorTable *AIGRasterBand::GetColorTable()
279
280
0
{
281
0
    AIGDataset *poODS = cpl::down_cast<AIGDataset *>(poDS);
282
283
0
    if (poODS->poCT != nullptr)
284
0
        return poODS->poCT;
285
286
0
    return GDALPamRasterBand::GetColorTable();
287
0
}
288
289
/************************************************************************/
290
/* ==================================================================== */
291
/*                            AIGDataset                               */
292
/* ==================================================================== */
293
/************************************************************************/
294
295
/************************************************************************/
296
/*                             AIGDataset()                             */
297
/************************************************************************/
298
299
AIGDataset::AIGDataset()
300
8.03k
    : psInfo(nullptr), papszPrj(nullptr), poCT(nullptr), bHasReadRat(false),
301
8.03k
      poRAT(nullptr)
302
8.03k
{
303
8.03k
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
304
8.03k
}
305
306
/************************************************************************/
307
/*                            ~AIGDataset()                             */
308
/************************************************************************/
309
310
AIGDataset::~AIGDataset()
311
312
8.03k
{
313
8.03k
    FlushCache(true);
314
8.03k
    CSLDestroy(papszPrj);
315
8.03k
    if (psInfo != nullptr)
316
8.03k
        AIGClose(psInfo);
317
318
8.03k
    if (poCT != nullptr)
319
193
        delete poCT;
320
321
8.03k
    if (poRAT != nullptr)
322
0
        delete poRAT;
323
8.03k
}
324
325
/************************************************************************/
326
/*                            GetFileList()                             */
327
/************************************************************************/
328
329
char **AIGDataset::GetFileList()
330
331
16.0k
{
332
16.0k
    char **papszFileList = GDALPamDataset::GetFileList();
333
334
    // Add in all files in the cover directory.
335
16.0k
    char **papszCoverFiles = VSIReadDir(GetDescription());
336
337
138k
    for (int i = 0; papszCoverFiles != nullptr && papszCoverFiles[i] != nullptr;
338
122k
         i++)
339
122k
    {
340
122k
        if (EQUAL(papszCoverFiles[i], ".") || EQUAL(papszCoverFiles[i], ".."))
341
2
            continue;
342
343
122k
        papszFileList = CSLAddString(
344
122k
            papszFileList,
345
122k
            CPLFormFilenameSafe(GetDescription(), papszCoverFiles[i], nullptr)
346
122k
                .c_str());
347
122k
    }
348
16.0k
    CSLDestroy(papszCoverFiles);
349
350
16.0k
    return papszFileList;
351
16.0k
}
352
353
/************************************************************************/
354
/*                       AIGErrorHandlerVATOpen()                       */
355
/************************************************************************/
356
357
class AIGErrorDescription
358
{
359
  public:
360
    CPLErr eErr;
361
    CPLErrorNum no;
362
    std::string osMsg;
363
};
364
365
static void CPL_STDCALL AIGErrorHandlerVATOpen(CPLErr eErr, CPLErrorNum no,
366
                                               const char *msg)
367
0
{
368
0
    std::vector<AIGErrorDescription> *paoErrors =
369
0
        (std::vector<AIGErrorDescription> *)CPLGetErrorHandlerUserData();
370
0
    if (STARTS_WITH_CI(msg, "EOF encountered in") &&
371
0
        strstr(msg, "../info/arc.dir") != nullptr)
372
0
        return;
373
0
    if (STARTS_WITH_CI(msg, "Failed to open table "))
374
0
        return;
375
0
    AIGErrorDescription oError;
376
0
    oError.eErr = eErr;
377
0
    oError.no = no;
378
0
    oError.osMsg = msg;
379
0
    paoErrors->push_back(std::move(oError));
380
0
}
381
382
/************************************************************************/
383
/*                              ReadRAT()                               */
384
/************************************************************************/
385
386
void AIGDataset::ReadRAT()
387
388
0
{
389
    /* -------------------------------------------------------------------- */
390
    /*      Check if we have an associated info directory.  If not          */
391
    /*      return quietly.                                                 */
392
    /* -------------------------------------------------------------------- */
393
0
    CPLString osInfoPath, osTableName;
394
0
    VSIStatBufL sStatBuf;
395
396
0
    osInfoPath = psInfo->pszCoverName;
397
0
    osInfoPath += "/../info";
398
399
0
    if (VSIStatL(osInfoPath, &sStatBuf) != 0)
400
0
    {
401
0
        CPLDebug("AIG", "No associated info directory at: %s, skip RAT.",
402
0
                 osInfoPath.c_str());
403
0
        return;
404
0
    }
405
406
0
    osInfoPath += "/";
407
408
    /* -------------------------------------------------------------------- */
409
    /*      Attempt to open the VAT table associated with this coverage.    */
410
    /* -------------------------------------------------------------------- */
411
0
    osTableName = CPLGetFilename(psInfo->pszCoverName);
412
0
    osTableName += ".VAT";
413
414
    /* Turn off errors that can be triggered if the info has no VAT */
415
    /* table related with this coverage */
416
0
    std::vector<AIGErrorDescription> aoErrors;
417
0
    CPLPushErrorHandlerEx(AIGErrorHandlerVATOpen, &aoErrors);
418
419
0
    AVCBinFile *psFile = AVCBinReadOpen(
420
0
        osInfoPath, osTableName, AVCCoverTypeUnknown, AVCFileTABLE, nullptr);
421
0
    CPLPopErrorHandler();
422
423
    /* Emit other errors */
424
0
    std::vector<AIGErrorDescription>::const_iterator oIter;
425
0
    for (oIter = aoErrors.begin(); oIter != aoErrors.end(); ++oIter)
426
0
    {
427
0
        const AIGErrorDescription &oError = *oIter;
428
0
        CPLError(oError.eErr, oError.no, "%s", oError.osMsg.c_str());
429
0
    }
430
431
0
    CPLErrorReset();
432
0
    if (psFile == nullptr)
433
0
        return;
434
435
0
    AVCTableDef *psTableDef = psFile->hdr.psTableDef;
436
437
    /* -------------------------------------------------------------------- */
438
    /*      Setup columns in corresponding RAT.                             */
439
    /* -------------------------------------------------------------------- */
440
0
    poRAT = new GDALDefaultRasterAttributeTable();
441
442
0
    for (int iField = 0; iField < psTableDef->numFields; iField++)
443
0
    {
444
0
        AVCFieldInfo *psFDef = psTableDef->pasFieldDef + iField;
445
0
        GDALRATFieldUsage eFUsage = GFU_Generic;
446
0
        GDALRATFieldType eFType = GFT_String;
447
448
0
        CPLString osFName = psFDef->szName;
449
0
        osFName.Trim();
450
451
0
        if (EQUAL(osFName, "VALUE"))
452
0
            eFUsage = GFU_MinMax;
453
0
        else if (EQUAL(osFName, "COUNT"))
454
0
            eFUsage = GFU_PixelCount;
455
456
0
        if (psFDef->nType1 * 10 == AVC_FT_BININT)
457
0
            eFType = GFT_Integer;
458
0
        else if (psFDef->nType1 * 10 == AVC_FT_BINFLOAT)
459
0
            eFType = GFT_Real;
460
461
0
        poRAT->CreateColumn(osFName, eFType, eFUsage);
462
0
    }
463
464
    /* -------------------------------------------------------------------- */
465
    /*      Process all records into RAT.                                   */
466
    /* -------------------------------------------------------------------- */
467
0
    AVCField *pasFields = nullptr;
468
0
    int iRecord = 0;
469
470
0
    while ((pasFields = AVCBinReadNextTableRec(psFile)) != nullptr)
471
0
    {
472
0
        iRecord++;
473
474
0
        for (int iField = 0; iField < psTableDef->numFields; iField++)
475
0
        {
476
0
            switch (psTableDef->pasFieldDef[iField].nType1 * 10)
477
0
            {
478
0
                case AVC_FT_DATE:
479
0
                case AVC_FT_FIXINT:
480
0
                case AVC_FT_CHAR:
481
0
                case AVC_FT_FIXNUM:
482
0
                {
483
                    // XXX - I bet mloskot would like to see const_cast +
484
                    // static_cast :-)
485
0
                    const char *pszTmp =
486
0
                        (const char *)(pasFields[iField].pszStr);
487
0
                    CPLString osStrValue(pszTmp);
488
0
                    poRAT->SetValue(iRecord - 1, iField,
489
0
                                    osStrValue.Trim().c_str());
490
0
                }
491
0
                break;
492
493
0
                case AVC_FT_BININT:
494
0
                    if (psTableDef->pasFieldDef[iField].nSize == 4)
495
0
                        poRAT->SetValue(iRecord - 1, iField,
496
0
                                        pasFields[iField].nInt32);
497
0
                    else
498
0
                        poRAT->SetValue(iRecord - 1, iField,
499
0
                                        pasFields[iField].nInt16);
500
0
                    break;
501
502
0
                case AVC_FT_BINFLOAT:
503
0
                    if (psTableDef->pasFieldDef[iField].nSize == 4)
504
0
                        poRAT->SetValue(iRecord - 1, iField,
505
0
                                        pasFields[iField].fFloat);
506
0
                    else
507
0
                        poRAT->SetValue(iRecord - 1, iField,
508
0
                                        pasFields[iField].dDouble);
509
0
                    break;
510
0
            }
511
0
        }
512
0
    }
513
514
    /* -------------------------------------------------------------------- */
515
    /*      Cleanup                                                         */
516
    /* -------------------------------------------------------------------- */
517
518
0
    AVCBinReadClose(psFile);
519
520
    /* Workaround against #2447 and #3031, to avoid binding languages */
521
    /* not being able to open the dataset */
522
0
    CPLErrorReset();
523
0
}
524
525
/************************************************************************/
526
/*                                Open()                                */
527
/************************************************************************/
528
529
GDALDataset *AIGDataset::Open(GDALOpenInfo *poOpenInfo)
530
531
560k
{
532
    /* -------------------------------------------------------------------- */
533
    /*      If the pass name ends in .adf assume a file within the          */
534
    /*      coverage has been selected, and strip that off the coverage     */
535
    /*      name.                                                           */
536
    /* -------------------------------------------------------------------- */
537
560k
    CPLString osCoverName;
538
539
560k
    osCoverName = poOpenInfo->pszFilename;
540
560k
    if (osCoverName.size() > 4 &&
541
549k
        EQUAL(osCoverName.c_str() + osCoverName.size() - 4, ".adf"))
542
9.75k
    {
543
9.75k
        osCoverName = CPLGetDirnameSafe(poOpenInfo->pszFilename);
544
9.75k
        if (osCoverName == "")
545
0
            osCoverName = ".";
546
9.75k
    }
547
548
    /* -------------------------------------------------------------------- */
549
    /*      Otherwise verify we were already given a directory.             */
550
    /* -------------------------------------------------------------------- */
551
550k
    else if (!poOpenInfo->bIsDirectory)
552
546k
    {
553
546k
        return nullptr;
554
546k
    }
555
556
    /* -------------------------------------------------------------------- */
557
    /*      Verify that a few of the "standard" files are available.        */
558
    /* -------------------------------------------------------------------- */
559
13.8k
    VSIStatBufL sStatBuf;
560
13.8k
    CPLString osTestName;
561
562
13.8k
    osTestName.Printf("%s/hdr.adf", osCoverName.c_str());
563
13.8k
    if (VSIStatL(osTestName, &sStatBuf) != 0)
564
8.94k
    {
565
8.94k
        osTestName.Printf("%s/HDR.ADF", osCoverName.c_str());
566
8.94k
        if (VSIStatL(osTestName, &sStatBuf) != 0)
567
5.35k
            return nullptr;
568
8.94k
    }
569
570
    /* -------------------------------------------------------------------- */
571
    /*      Confirm we have at least one raster data file.  These can be    */
572
    /*      sparse so we don't require particular ones to exists but if     */
573
    /*      there are none this is likely not a grid.                       */
574
    /* -------------------------------------------------------------------- */
575
8.52k
    char **papszFileList = VSIReadDir(osCoverName);
576
8.52k
    bool bGotOne = false;
577
578
8.52k
    if (papszFileList == nullptr)
579
0
    {
580
        /* Useful when reading from /vsicurl/ on servers that don't */
581
        /* return a file list */
582
        /* such as
583
         * /vsicurl/http://eros.usgs.gov/archive/nslrsda/GeoTowns/NLCD/89110458
584
         */
585
0
        do
586
0
        {
587
0
            osTestName.Printf("%s/W001001.ADF", osCoverName.c_str());
588
0
            if (VSIStatL(osTestName, &sStatBuf) == 0)
589
0
            {
590
0
                bGotOne = true;
591
0
                break;
592
0
            }
593
594
0
            osTestName.Printf("%s/w001001.adf", osCoverName.c_str());
595
0
            if (VSIStatL(osTestName, &sStatBuf) == 0)
596
0
            {
597
0
                bGotOne = true;
598
0
                break;
599
0
            }
600
0
        } while (false);
601
0
    }
602
603
40.5k
    for (int iFile = 0; papszFileList != nullptr &&
604
40.5k
                        papszFileList[iFile] != nullptr && !bGotOne;
605
32.0k
         iFile++)
606
32.0k
    {
607
32.0k
        if (strlen(papszFileList[iFile]) != 11)
608
23.3k
            continue;
609
610
        // looking for something like w001001.adf or z001013.adf
611
8.75k
        if (papszFileList[iFile][0] != 'w' && papszFileList[iFile][0] != 'W' &&
612
6.60k
            papszFileList[iFile][0] != 'z' && papszFileList[iFile][0] != 'Z')
613
212
            continue;
614
615
8.54k
        if (!STARTS_WITH(papszFileList[iFile] + 1, "0010"))
616
118
            continue;
617
618
8.42k
        if (!EQUAL(papszFileList[iFile] + 7, ".adf"))
619
33
            continue;
620
621
8.39k
        bGotOne = true;
622
8.39k
    }
623
8.52k
    CSLDestroy(papszFileList);
624
625
8.52k
    if (!bGotOne)
626
135
        return nullptr;
627
628
    /* -------------------------------------------------------------------- */
629
    /*      Open the file.                                                  */
630
    /* -------------------------------------------------------------------- */
631
8.39k
    AIGInfo_t *psInfo = AIGOpen(osCoverName.c_str(), "r");
632
633
8.39k
    if (psInfo == nullptr)
634
355
    {
635
355
        CPLErrorReset();
636
355
        return nullptr;
637
355
    }
638
639
    /* -------------------------------------------------------------------- */
640
    /*      Confirm the requested access is supported.                      */
641
    /* -------------------------------------------------------------------- */
642
8.03k
    if (poOpenInfo->eAccess == GA_Update)
643
0
    {
644
0
        AIGClose(psInfo);
645
0
        ReportUpdateNotSupportedByDriver("AIG");
646
0
        return nullptr;
647
0
    }
648
    /* -------------------------------------------------------------------- */
649
    /*      Create a corresponding GDALDataset.                             */
650
    /* -------------------------------------------------------------------- */
651
8.03k
    AIGDataset *poDS = new AIGDataset();
652
653
8.03k
    poDS->psInfo = psInfo;
654
655
    /* -------------------------------------------------------------------- */
656
    /*      Try to read a color table (.clr).  It seems it is legal to      */
657
    /*      have more than one so we just use the first one found.          */
658
    /* -------------------------------------------------------------------- */
659
8.03k
    char **papszFiles = VSIReadDir(psInfo->pszCoverName);
660
8.03k
    CPLString osClrFilename;
661
8.03k
    CPLString osCleanPath = CPLCleanTrailingSlashSafe(psInfo->pszCoverName);
662
663
    // first check for any .clr in coverage dir.
664
67.5k
    for (int iFile = 0; papszFiles != nullptr && papszFiles[iFile] != nullptr;
665
59.5k
         iFile++)
666
59.7k
    {
667
59.7k
        const std::string osExt = CPLGetExtensionSafe(papszFiles[iFile]);
668
59.7k
        if (!EQUAL(osExt.c_str(), "clr") && !EQUAL(osExt.c_str(), "CLR"))
669
59.5k
            continue;
670
671
204
        osClrFilename = CPLFormFilenameSafe(psInfo->pszCoverName,
672
204
                                            papszFiles[iFile], nullptr);
673
204
        break;
674
59.7k
    }
675
676
8.03k
    CSLDestroy(papszFiles);
677
678
    // Look in parent if we don't find a .clr in the coverage dir.
679
8.03k
    if (osClrFilename.empty())
680
7.83k
    {
681
7.83k
        CPLString osTestClrFilename;
682
7.83k
        osTestClrFilename.Printf("%s/../%s.clr", psInfo->pszCoverName,
683
7.83k
                                 CPLGetFilename(osCleanPath));
684
685
7.83k
        if (VSIStatL(osTestClrFilename, &sStatBuf) != 0)
686
7.83k
        {
687
7.83k
            osTestClrFilename.Printf("%s/../%s.CLR", psInfo->pszCoverName,
688
7.83k
                                     CPLGetFilename(osCleanPath));
689
690
7.83k
            if (!VSIStatL(osTestClrFilename, &sStatBuf))
691
0
                osClrFilename = std::move(osTestClrFilename);
692
7.83k
        }
693
0
        else
694
0
            osClrFilename = std::move(osTestClrFilename);
695
7.83k
    }
696
697
8.03k
    if (!osClrFilename.empty())
698
204
        poDS->TranslateColorTable(osClrFilename);
699
700
    /* -------------------------------------------------------------------- */
701
    /*      Establish raster info.                                          */
702
    /* -------------------------------------------------------------------- */
703
8.03k
    poDS->nRasterXSize = psInfo->nPixels;
704
8.03k
    poDS->nRasterYSize = psInfo->nLines;
705
8.03k
    poDS->nBands = 1;
706
707
    /* -------------------------------------------------------------------- */
708
    /*      Create band information objects.                                */
709
    /* -------------------------------------------------------------------- */
710
8.03k
    poDS->SetBand(1, new AIGRasterBand(poDS, 1));
711
712
    /* -------------------------------------------------------------------- */
713
    /*      Try to read projection file.                                    */
714
    /* -------------------------------------------------------------------- */
715
8.03k
    const std::string osPrjFilename =
716
8.03k
        CPLFormCIFilenameSafe(psInfo->pszCoverName, "prj", "adf");
717
8.03k
    if (VSIStatL(osPrjFilename.c_str(), &sStatBuf) == 0)
718
7.53k
    {
719
7.53k
        OGRSpatialReference oSRS;
720
7.53k
        oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
721
722
7.53k
        poDS->papszPrj = CSLLoad(osPrjFilename.c_str());
723
724
7.53k
        if (oSRS.importFromESRI(poDS->papszPrj) == OGRERR_NONE)
725
5.57k
        {
726
            // If geographic values are in seconds, we must transform.
727
            // Is there a code for minutes too?
728
5.57k
            if (oSRS.IsGeographic() &&
729
529
                EQUAL(OSR_GDS(poDS->papszPrj, "Units", ""), "DS"))
730
0
            {
731
0
                psInfo->dfLLX /= 3600.0;
732
0
                psInfo->dfURY /= 3600.0;
733
0
                psInfo->dfCellSizeX /= 3600.0;
734
0
                psInfo->dfCellSizeY /= 3600.0;
735
0
            }
736
737
5.57k
            poDS->m_oSRS = std::move(oSRS);
738
5.57k
        }
739
7.53k
    }
740
741
    /* -------------------------------------------------------------------- */
742
    /*      Initialize any PAM information.                                 */
743
    /* -------------------------------------------------------------------- */
744
8.03k
    poDS->SetDescription(psInfo->pszCoverName);
745
8.03k
    poDS->TryLoadXML();
746
747
    /* -------------------------------------------------------------------- */
748
    /*      Open overviews.                                                 */
749
    /* -------------------------------------------------------------------- */
750
8.03k
    poDS->oOvManager.Initialize(poDS, psInfo->pszCoverName);
751
752
8.03k
    return poDS;
753
8.03k
}
754
755
/************************************************************************/
756
/*                          GetGeoTransform()                           */
757
/************************************************************************/
758
759
CPLErr AIGDataset::GetGeoTransform(GDALGeoTransform &gt) const
760
761
8.03k
{
762
8.03k
    gt.xorig = psInfo->dfLLX;
763
8.03k
    gt.xscale = psInfo->dfCellSizeX;
764
8.03k
    gt.xrot = 0;
765
766
8.03k
    gt.yorig = psInfo->dfURY;
767
8.03k
    gt.yrot = 0;
768
8.03k
    gt.yscale = -psInfo->dfCellSizeY;
769
770
8.03k
    return CE_None;
771
8.03k
}
772
773
/************************************************************************/
774
/*                           GetSpatialRef()                            */
775
/************************************************************************/
776
777
const OGRSpatialReference *AIGDataset::GetSpatialRef() const
778
779
8.03k
{
780
8.03k
    return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
781
8.03k
}
782
783
/************************************************************************/
784
/*                        TranslateColorTable()                         */
785
/************************************************************************/
786
787
void AIGDataset::TranslateColorTable(const char *pszClrFilename)
788
789
204
{
790
204
    char **papszClrLines = CSLLoad(pszClrFilename);
791
204
    if (papszClrLines == nullptr)
792
11
        return;
793
794
193
    poCT = new GDALColorTable();
795
796
1.35M
    for (int iLine = 0; papszClrLines[iLine] != nullptr; iLine++)
797
1.35M
    {
798
1.35M
        char **papszTokens = CSLTokenizeString(papszClrLines[iLine]);
799
800
1.35M
        if (CSLCount(papszTokens) >= 4 && papszTokens[0][0] != '#')
801
5.46k
        {
802
5.46k
            int nIndex;
803
5.46k
            GDALColorEntry sEntry;
804
805
5.46k
            nIndex = atoi(papszTokens[0]);
806
5.46k
            sEntry.c1 = (short)atoi(papszTokens[1]);
807
5.46k
            sEntry.c2 = (short)atoi(papszTokens[2]);
808
5.46k
            sEntry.c3 = (short)atoi(papszTokens[3]);
809
5.46k
            sEntry.c4 = 255;
810
811
5.46k
            if ((nIndex < 0 || nIndex > 33000) ||
812
5.43k
                (sEntry.c1 < 0 || sEntry.c1 > 255) ||
813
5.42k
                (sEntry.c2 < 0 || sEntry.c2 > 255) ||
814
5.41k
                (sEntry.c3 < 0 || sEntry.c3 > 255))
815
60
            {
816
60
                CSLDestroy(papszTokens);
817
60
                CPLError(CE_Failure, CPLE_AppDefined,
818
60
                         "Color table entry appears to be corrupt, skipping "
819
60
                         "the rest. ");
820
60
                break;
821
60
            }
822
823
5.40k
            poCT->SetColorEntry(nIndex, &sEntry);
824
5.40k
        }
825
826
1.35M
        CSLDestroy(papszTokens);
827
1.35M
    }
828
829
193
    CSLDestroy(papszClrLines);
830
193
}
831
832
/************************************************************************/
833
/*                              OSR_GDS()                               */
834
/************************************************************************/
835
836
static CPLString OSR_GDS(char **papszNV, const char *pszField,
837
                         const char *pszDefaultValue)
838
839
529
{
840
529
    if (papszNV == nullptr || papszNV[0] == nullptr)
841
0
        return pszDefaultValue;
842
843
529
    int iLine = 0;
844
698k
    for (; papszNV[iLine] != nullptr &&
845
697k
           !EQUALN(papszNV[iLine], pszField, strlen(pszField));
846
697k
         iLine++)
847
697k
    {
848
697k
    }
849
850
529
    if (papszNV[iLine] == nullptr)
851
500
        return pszDefaultValue;
852
29
    else
853
29
    {
854
29
        CPLString osResult;
855
29
        char **papszTokens = CSLTokenizeString(papszNV[iLine]);
856
857
29
        if (CSLCount(papszTokens) > 1)
858
28
            osResult = papszTokens[1];
859
1
        else
860
1
            osResult = pszDefaultValue;
861
862
29
        CSLDestroy(papszTokens);
863
29
        return osResult;
864
29
    }
865
529
}
866
867
/************************************************************************/
868
/*                             AIGRename()                              */
869
/*                                                                      */
870
/*      Custom renamer for AIG dataset.                                 */
871
/************************************************************************/
872
873
static CPLErr AIGRename(const char *pszNewName, const char *pszOldName)
874
875
0
{
876
    /* -------------------------------------------------------------------- */
877
    /*      Make sure we are talking about paths to the coverage            */
878
    /*      directory.                                                      */
879
    /* -------------------------------------------------------------------- */
880
0
    CPLString osOldPath, osNewPath;
881
882
0
    if (!CPLGetExtensionSafe(pszNewName).empty())
883
0
        osNewPath = CPLGetPathSafe(pszNewName);
884
0
    else
885
0
        osNewPath = pszNewName;
886
887
0
    if (!CPLGetExtensionSafe(pszOldName).empty())
888
0
        osOldPath = CPLGetPathSafe(pszOldName);
889
0
    else
890
0
        osOldPath = pszOldName;
891
892
    /* -------------------------------------------------------------------- */
893
    /*      Get file list.                                                  */
894
    /* -------------------------------------------------------------------- */
895
0
    GDALOpenInfo oOpenInfo(osOldPath, GA_ReadOnly);
896
0
    GDALDatasetH hDS = GDALDataset::ToHandle(AIGDataset::Open(&oOpenInfo));
897
0
    if (hDS == nullptr)
898
0
        return CE_Failure;
899
900
0
    const CPLStringList aosFileList(GDALGetFileList(hDS));
901
0
    GDALClose(hDS);
902
903
0
    if (aosFileList.empty())
904
0
        return CE_Failure;
905
906
    /* -------------------------------------------------------------------- */
907
    /*      Work out the corresponding new names.                           */
908
    /* -------------------------------------------------------------------- */
909
0
    CPLStringList aosNewFileList;
910
911
0
    for (const char *pszFilename : cpl::Iterate(aosFileList))
912
0
    {
913
0
        CPLString osNewFilename;
914
915
0
        if (!EQUALN(pszFilename, osOldPath, osOldPath.size()))
916
0
        {
917
0
            CPLAssert(false);
918
0
            return CE_Failure;
919
0
        }
920
921
0
        osNewFilename = osNewPath + (pszFilename + osOldPath.size());
922
923
0
        aosNewFileList.push_back(osNewFilename);
924
0
    }
925
926
    /* -------------------------------------------------------------------- */
927
    /*      Try renaming the directory.                                     */
928
    /* -------------------------------------------------------------------- */
929
0
    if (VSIRename(osNewPath, osOldPath) != 0)
930
0
    {
931
0
        if (VSIMkdir(osNewPath, 0777) != 0)
932
0
        {
933
0
            CPLError(CE_Failure, CPLE_AppDefined,
934
0
                     "Unable to create directory %s:\n%s", osNewPath.c_str(),
935
0
                     VSIStrerror(errno));
936
0
            return CE_Failure;
937
0
        }
938
0
    }
939
940
    /* -------------------------------------------------------------------- */
941
    /*      Copy/rename any remaining files.                                */
942
    /* -------------------------------------------------------------------- */
943
0
    VSIStatBufL sStatBuf;
944
945
0
    for (int i = 0; i < aosFileList.size() && i < aosNewFileList.size(); ++i)
946
0
    {
947
0
        if (VSIStatL(aosFileList[i], &sStatBuf) == 0 &&
948
0
            VSI_ISREG(sStatBuf.st_mode))
949
0
        {
950
0
            if (CPLMoveFile(aosNewFileList[i], aosFileList[i]) != 0)
951
0
            {
952
0
                CPLError(CE_Failure, CPLE_AppDefined,
953
0
                         "Unable to move %s to %s:\n%s", aosFileList[i],
954
0
                         aosNewFileList[i], VSIStrerror(errno));
955
0
                return CE_Failure;
956
0
            }
957
0
        }
958
0
    }
959
960
0
    if (VSIStatL(osOldPath, &sStatBuf) == 0)
961
0
    {
962
0
        if (CPLUnlinkTree(osOldPath) != 0)
963
0
        {
964
0
            CPLError(CE_Warning, CPLE_AppDefined,
965
0
                     "Unable to cleanup old path.");
966
0
        }
967
0
    }
968
969
0
    return CE_None;
970
0
}
971
972
/************************************************************************/
973
/*                             AIGDelete()                              */
974
/*                                                                      */
975
/*      Custom dataset deleter for AIG dataset.                         */
976
/************************************************************************/
977
978
static CPLErr AIGDelete(const char *pszDatasetname)
979
980
0
{
981
    /* -------------------------------------------------------------------- */
982
    /*      Get file list.                                                  */
983
    /* -------------------------------------------------------------------- */
984
0
    GDALOpenInfo oOpenInfo(pszDatasetname, GA_ReadOnly);
985
0
    GDALDatasetH hDS = GDALDataset::ToHandle(AIGDataset::Open(&oOpenInfo));
986
0
    if (hDS == nullptr)
987
0
        return CE_Failure;
988
989
0
    char **papszFileList = GDALGetFileList(hDS);
990
0
    GDALClose(hDS);
991
992
0
    if (papszFileList == nullptr)
993
0
        return CE_Failure;
994
995
    /* -------------------------------------------------------------------- */
996
    /*      Delete all regular files.                                       */
997
    /* -------------------------------------------------------------------- */
998
0
    for (int i = 0; papszFileList[i] != nullptr; i++)
999
0
    {
1000
0
        VSIStatBufL sStatBuf;
1001
0
        if (VSIStatL(papszFileList[i], &sStatBuf) == 0 &&
1002
0
            VSI_ISREG(sStatBuf.st_mode))
1003
0
        {
1004
0
            if (VSIUnlink(papszFileList[i]) != 0)
1005
0
            {
1006
0
                CPLError(CE_Failure, CPLE_AppDefined,
1007
0
                         "Unable to delete '%s':\n%s", papszFileList[i],
1008
0
                         VSIStrerror(errno));
1009
0
                return CE_Failure;
1010
0
            }
1011
0
        }
1012
0
    }
1013
1014
    /* -------------------------------------------------------------------- */
1015
    /*      Delete directories.                                             */
1016
    /* -------------------------------------------------------------------- */
1017
0
    for (int i = 0; papszFileList[i] != nullptr; i++)
1018
0
    {
1019
0
        VSIStatBufL sStatBuf;
1020
0
        if (VSIStatL(papszFileList[i], &sStatBuf) == 0 &&
1021
0
            VSI_ISDIR(sStatBuf.st_mode))
1022
0
        {
1023
0
            if (CPLUnlinkTree(papszFileList[i]) != 0)
1024
0
                return CE_Failure;
1025
0
        }
1026
0
    }
1027
1028
0
    return CE_None;
1029
0
}
1030
1031
/************************************************************************/
1032
/*                          GDALRegister_AIG()                          */
1033
/************************************************************************/
1034
1035
void GDALRegister_AIGrid()
1036
1037
24
{
1038
24
    if (GDALGetDriverByName("AIG") != nullptr)
1039
0
        return;
1040
1041
24
    GDALDriver *poDriver = new GDALDriver();
1042
1043
24
    poDriver->SetDescription("AIG");
1044
24
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1045
24
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Arc/Info Binary Grid");
1046
24
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/aig.html");
1047
24
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1048
1049
24
    poDriver->pfnOpen = AIGDataset::Open;
1050
1051
24
    poDriver->pfnRename = AIGRename;
1052
24
    poDriver->pfnDelete = AIGDelete;
1053
1054
24
    GetGDALDriverManager()->RegisterDriver(poDriver);
1055
24
}