Coverage Report

Created: 2025-11-15 08:43

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
7.60k
{
99
7.60k
    poDS = poDSIn;
100
7.60k
    nBand = nBandIn;
101
102
7.60k
    nBlockXSize = poDSIn->psInfo->nBlockXSize;
103
7.60k
    nBlockYSize = poDSIn->psInfo->nBlockYSize;
104
105
7.60k
    if (poDSIn->psInfo->nCellType == AIG_CELLTYPE_INT &&
106
2.13k
        poDSIn->psInfo->dfMin >= 0.0 && poDSIn->psInfo->dfMax <= 254.0)
107
124
    {
108
124
        eDataType = GDT_Byte;
109
124
    }
110
7.48k
    else if (poDSIn->psInfo->nCellType == AIG_CELLTYPE_INT &&
111
2.01k
             poDSIn->psInfo->dfMin >= -32767 && poDSIn->psInfo->dfMax <= 32767)
112
46
    {
113
46
        eDataType = GDT_Int16;
114
46
    }
115
7.43k
    else if (poDSIn->psInfo->nCellType == AIG_CELLTYPE_INT)
116
1.96k
    {
117
1.96k
        eDataType = GDT_Int32;
118
1.96k
    }
119
5.46k
    else
120
5.46k
    {
121
5.46k
        eDataType = GDT_Float32;
122
5.46k
    }
123
7.60k
}
124
125
/************************************************************************/
126
/*                             IReadBlock()                             */
127
/************************************************************************/
128
129
CPLErr AIGRasterBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
130
131
687k
{
132
687k
    AIGDataset *poODS = cpl::down_cast<AIGDataset *>(poDS);
133
687k
    GInt32 *panGridRaster;
134
135
687k
    if (poODS->psInfo->nCellType == AIG_CELLTYPE_INT)
136
49.8k
    {
137
49.8k
        panGridRaster = (GInt32 *)VSIMalloc3(4, nBlockXSize, nBlockYSize);
138
49.8k
        if (panGridRaster == nullptr ||
139
49.8k
            AIGReadTile(poODS->psInfo, nBlockXOff, nBlockYOff, panGridRaster) !=
140
49.8k
                CE_None)
141
96
        {
142
96
            CPLFree(panGridRaster);
143
96
            return CE_Failure;
144
96
        }
145
146
49.7k
        if (eDataType == GDT_Byte)
147
7.32k
        {
148
19.8M
            for (int i = 0; i < nBlockXSize * nBlockYSize; i++)
149
19.8M
            {
150
19.8M
                if (panGridRaster[i] == ESRI_GRID_NO_DATA)
151
19.8M
                    ((GByte *)pImage)[i] = 255;
152
8.74k
                else
153
8.74k
                    ((GByte *)pImage)[i] = (GByte)panGridRaster[i];
154
19.8M
            }
155
7.32k
        }
156
42.4k
        else if (eDataType == GDT_Int16)
157
2.65k
        {
158
6.60M
            for (int i = 0; i < nBlockXSize * nBlockYSize; i++)
159
6.60M
            {
160
6.60M
                if (panGridRaster[i] == ESRI_GRID_NO_DATA)
161
6.48M
                    ((GInt16 *)pImage)[i] = -32768;
162
123k
                else
163
123k
                    ((GInt16 *)pImage)[i] = (GInt16)panGridRaster[i];
164
6.60M
            }
165
2.65k
        }
166
39.7k
        else
167
39.7k
        {
168
57.7M
            for (int i = 0; i < nBlockXSize * nBlockYSize; i++)
169
57.7M
                ((GInt32 *)pImage)[i] = panGridRaster[i];
170
39.7k
        }
171
172
49.7k
        CPLFree(panGridRaster);
173
174
49.7k
        return CE_None;
175
49.8k
    }
176
637k
    else
177
637k
    {
178
637k
        return AIGReadFloatTile(poODS->psInfo, nBlockXOff, nBlockYOff,
179
637k
                                (float *)pImage);
180
637k
    }
181
687k
}
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
30.4k
{
244
30.4k
    if (pbSuccess != nullptr)
245
22.8k
        *pbSuccess = TRUE;
246
247
30.4k
    if (eDataType == GDT_Float32)
248
21.8k
        return ESRI_GRID_FLOAT_NO_DATA;
249
250
8.54k
    if (eDataType == GDT_Int16)
251
184
        return -32768;
252
253
8.36k
    if (eDataType == GDT_Byte)
254
496
        return 255;
255
256
7.86k
    return ESRI_GRID_NO_DATA;
257
8.36k
}
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
7.60k
    : psInfo(nullptr), papszPrj(nullptr), poCT(nullptr), bHasReadRat(false),
301
7.60k
      poRAT(nullptr)
302
7.60k
{
303
7.60k
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
304
7.60k
}
305
306
/************************************************************************/
307
/*                           ~AIGDataset()                            */
308
/************************************************************************/
309
310
AIGDataset::~AIGDataset()
311
312
7.60k
{
313
7.60k
    FlushCache(true);
314
7.60k
    CSLDestroy(papszPrj);
315
7.60k
    if (psInfo != nullptr)
316
7.60k
        AIGClose(psInfo);
317
318
7.60k
    if (poCT != nullptr)
319
235
        delete poCT;
320
321
7.60k
    if (poRAT != nullptr)
322
0
        delete poRAT;
323
7.60k
}
324
325
/************************************************************************/
326
/*                            GetFileList()                             */
327
/************************************************************************/
328
329
char **AIGDataset::GetFileList()
330
331
15.2k
{
332
15.2k
    char **papszFileList = GDALPamDataset::GetFileList();
333
334
    // Add in all files in the cover directory.
335
15.2k
    char **papszCoverFiles = VSIReadDir(GetDescription());
336
337
129k
    for (int i = 0; papszCoverFiles != nullptr && papszCoverFiles[i] != nullptr;
338
114k
         i++)
339
114k
    {
340
114k
        if (EQUAL(papszCoverFiles[i], ".") || EQUAL(papszCoverFiles[i], ".."))
341
2
            continue;
342
343
114k
        papszFileList = CSLAddString(
344
114k
            papszFileList,
345
114k
            CPLFormFilenameSafe(GetDescription(), papszCoverFiles[i], nullptr)
346
114k
                .c_str());
347
114k
    }
348
15.2k
    CSLDestroy(papszCoverFiles);
349
350
15.2k
    return papszFileList;
351
15.2k
}
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
668k
{
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
668k
    CPLString osCoverName;
538
539
668k
    osCoverName = poOpenInfo->pszFilename;
540
668k
    if (osCoverName.size() > 4 &&
541
655k
        EQUAL(osCoverName.c_str() + osCoverName.size() - 4, ".adf"))
542
9.23k
    {
543
9.23k
        osCoverName = CPLGetDirnameSafe(poOpenInfo->pszFilename);
544
9.23k
        if (osCoverName == "")
545
0
            osCoverName = ".";
546
9.23k
    }
547
548
    /* -------------------------------------------------------------------- */
549
    /*      Otherwise verify we were already given a directory.             */
550
    /* -------------------------------------------------------------------- */
551
659k
    else if (!poOpenInfo->bIsDirectory)
552
654k
    {
553
654k
        return nullptr;
554
654k
    }
555
556
    /* -------------------------------------------------------------------- */
557
    /*      Verify that a few of the "standard" files are available.        */
558
    /* -------------------------------------------------------------------- */
559
14.1k
    VSIStatBufL sStatBuf;
560
14.1k
    CPLString osTestName;
561
562
14.1k
    osTestName.Printf("%s/hdr.adf", osCoverName.c_str());
563
14.1k
    if (VSIStatL(osTestName, &sStatBuf) != 0)
564
9.80k
    {
565
9.80k
        osTestName.Printf("%s/HDR.ADF", osCoverName.c_str());
566
9.80k
        if (VSIStatL(osTestName, &sStatBuf) != 0)
567
5.93k
            return nullptr;
568
9.80k
    }
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.19k
    char **papszFileList = VSIReadDir(osCoverName);
576
8.19k
    bool bGotOne = false;
577
578
8.19k
    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
38.4k
    for (int iFile = 0; papszFileList != nullptr &&
604
38.4k
                        papszFileList[iFile] != nullptr && !bGotOne;
605
30.2k
         iFile++)
606
30.2k
    {
607
30.2k
        if (strlen(papszFileList[iFile]) != 11)
608
21.9k
            continue;
609
610
        // looking for something like w001001.adf or z001013.adf
611
8.29k
        if (papszFileList[iFile][0] != 'w' && papszFileList[iFile][0] != 'W' &&
612
5.88k
            papszFileList[iFile][0] != 'z' && papszFileList[iFile][0] != 'Z')
613
181
            continue;
614
615
8.11k
        if (!STARTS_WITH(papszFileList[iFile] + 1, "0010"))
616
112
            continue;
617
618
8.00k
        if (!EQUAL(papszFileList[iFile] + 7, ".adf"))
619
37
            continue;
620
621
7.96k
        bGotOne = true;
622
7.96k
    }
623
8.19k
    CSLDestroy(papszFileList);
624
625
8.19k
    if (!bGotOne)
626
235
        return nullptr;
627
628
    /* -------------------------------------------------------------------- */
629
    /*      Open the file.                                                  */
630
    /* -------------------------------------------------------------------- */
631
7.96k
    AIGInfo_t *psInfo = AIGOpen(osCoverName.c_str(), "r");
632
633
7.96k
    if (psInfo == nullptr)
634
359
    {
635
359
        CPLErrorReset();
636
359
        return nullptr;
637
359
    }
638
639
    /* -------------------------------------------------------------------- */
640
    /*      Confirm the requested access is supported.                      */
641
    /* -------------------------------------------------------------------- */
642
7.60k
    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
7.60k
    AIGDataset *poDS = new AIGDataset();
652
653
7.60k
    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
7.60k
    char **papszFiles = VSIReadDir(psInfo->pszCoverName);
660
7.60k
    CPLString osClrFilename;
661
7.60k
    CPLString osCleanPath = CPLCleanTrailingSlashSafe(psInfo->pszCoverName);
662
663
    // first check for any .clr in coverage dir.
664
63.3k
    for (int iFile = 0; papszFiles != nullptr && papszFiles[iFile] != nullptr;
665
55.7k
         iFile++)
666
55.9k
    {
667
55.9k
        const std::string osExt = CPLGetExtensionSafe(papszFiles[iFile]);
668
55.9k
        if (!EQUAL(osExt.c_str(), "clr") && !EQUAL(osExt.c_str(), "CLR"))
669
55.7k
            continue;
670
671
248
        osClrFilename = CPLFormFilenameSafe(psInfo->pszCoverName,
672
248
                                            papszFiles[iFile], nullptr);
673
248
        break;
674
55.9k
    }
675
676
7.60k
    CSLDestroy(papszFiles);
677
678
    // Look in parent if we don't find a .clr in the coverage dir.
679
7.60k
    if (osClrFilename.empty())
680
7.35k
    {
681
7.35k
        CPLString osTestClrFilename;
682
7.35k
        osTestClrFilename.Printf("%s/../%s.clr", psInfo->pszCoverName,
683
7.35k
                                 CPLGetFilename(osCleanPath));
684
685
7.35k
        if (VSIStatL(osTestClrFilename, &sStatBuf) != 0)
686
7.35k
        {
687
7.35k
            osTestClrFilename.Printf("%s/../%s.CLR", psInfo->pszCoverName,
688
7.35k
                                     CPLGetFilename(osCleanPath));
689
690
7.35k
            if (!VSIStatL(osTestClrFilename, &sStatBuf))
691
0
                osClrFilename = std::move(osTestClrFilename);
692
7.35k
        }
693
0
        else
694
0
            osClrFilename = std::move(osTestClrFilename);
695
7.35k
    }
696
697
7.60k
    if (!osClrFilename.empty())
698
248
        poDS->TranslateColorTable(osClrFilename);
699
700
    /* -------------------------------------------------------------------- */
701
    /*      Establish raster info.                                          */
702
    /* -------------------------------------------------------------------- */
703
7.60k
    poDS->nRasterXSize = psInfo->nPixels;
704
7.60k
    poDS->nRasterYSize = psInfo->nLines;
705
7.60k
    poDS->nBands = 1;
706
707
    /* -------------------------------------------------------------------- */
708
    /*      Create band information objects.                                */
709
    /* -------------------------------------------------------------------- */
710
7.60k
    poDS->SetBand(1, new AIGRasterBand(poDS, 1));
711
712
    /* -------------------------------------------------------------------- */
713
    /*      Try to read projection file.                                    */
714
    /* -------------------------------------------------------------------- */
715
7.60k
    const std::string osPrjFilename =
716
7.60k
        CPLFormCIFilenameSafe(psInfo->pszCoverName, "prj", "adf");
717
7.60k
    if (VSIStatL(osPrjFilename.c_str(), &sStatBuf) == 0)
718
7.09k
    {
719
7.09k
        OGRSpatialReference oSRS;
720
7.09k
        oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
721
722
7.09k
        poDS->papszPrj = CSLLoad(osPrjFilename.c_str());
723
724
7.09k
        if (oSRS.importFromESRI(poDS->papszPrj) == OGRERR_NONE)
725
5.14k
        {
726
            // If geographic values are in seconds, we must transform.
727
            // Is there a code for minutes too?
728
5.14k
            if (oSRS.IsGeographic() &&
729
538
                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.14k
            poDS->m_oSRS = std::move(oSRS);
738
5.14k
        }
739
7.09k
    }
740
741
    /* -------------------------------------------------------------------- */
742
    /*      Initialize any PAM information.                                 */
743
    /* -------------------------------------------------------------------- */
744
7.60k
    poDS->SetDescription(psInfo->pszCoverName);
745
7.60k
    poDS->TryLoadXML();
746
747
    /* -------------------------------------------------------------------- */
748
    /*      Open overviews.                                                 */
749
    /* -------------------------------------------------------------------- */
750
7.60k
    poDS->oOvManager.Initialize(poDS, psInfo->pszCoverName);
751
752
7.60k
    return poDS;
753
7.60k
}
754
755
/************************************************************************/
756
/*                          GetGeoTransform()                           */
757
/************************************************************************/
758
759
CPLErr AIGDataset::GetGeoTransform(GDALGeoTransform &gt) const
760
761
7.60k
{
762
7.60k
    gt[0] = psInfo->dfLLX;
763
7.60k
    gt[1] = psInfo->dfCellSizeX;
764
7.60k
    gt[2] = 0;
765
766
7.60k
    gt[3] = psInfo->dfURY;
767
7.60k
    gt[4] = 0;
768
7.60k
    gt[5] = -psInfo->dfCellSizeY;
769
770
7.60k
    return CE_None;
771
7.60k
}
772
773
/************************************************************************/
774
/*                          GetSpatialRef()                             */
775
/************************************************************************/
776
777
const OGRSpatialReference *AIGDataset::GetSpatialRef() const
778
779
7.60k
{
780
7.60k
    return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
781
7.60k
}
782
783
/************************************************************************/
784
/*                        TranslateColorTable()                         */
785
/************************************************************************/
786
787
void AIGDataset::TranslateColorTable(const char *pszClrFilename)
788
789
248
{
790
248
    char **papszClrLines = CSLLoad(pszClrFilename);
791
248
    if (papszClrLines == nullptr)
792
13
        return;
793
794
235
    poCT = new GDALColorTable();
795
796
1.27M
    for (int iLine = 0; papszClrLines[iLine] != nullptr; iLine++)
797
1.27M
    {
798
1.27M
        char **papszTokens = CSLTokenizeString(papszClrLines[iLine]);
799
800
1.27M
        if (CSLCount(papszTokens) >= 4 && papszTokens[0][0] != '#')
801
2.67k
        {
802
2.67k
            int nIndex;
803
2.67k
            GDALColorEntry sEntry;
804
805
2.67k
            nIndex = atoi(papszTokens[0]);
806
2.67k
            sEntry.c1 = (short)atoi(papszTokens[1]);
807
2.67k
            sEntry.c2 = (short)atoi(papszTokens[2]);
808
2.67k
            sEntry.c3 = (short)atoi(papszTokens[3]);
809
2.67k
            sEntry.c4 = 255;
810
811
2.67k
            if ((nIndex < 0 || nIndex > 33000) ||
812
2.64k
                (sEntry.c1 < 0 || sEntry.c1 > 255) ||
813
2.63k
                (sEntry.c2 < 0 || sEntry.c2 > 255) ||
814
2.62k
                (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
2.61k
            poCT->SetColorEntry(nIndex, &sEntry);
824
2.61k
        }
825
826
1.27M
        CSLDestroy(papszTokens);
827
1.27M
    }
828
829
235
    CSLDestroy(papszClrLines);
830
235
}
831
832
/************************************************************************/
833
/*                              OSR_GDS()                               */
834
/************************************************************************/
835
836
static CPLString OSR_GDS(char **papszNV, const char *pszField,
837
                         const char *pszDefaultValue)
838
839
538
{
840
538
    if (papszNV == nullptr || papszNV[0] == nullptr)
841
0
        return pszDefaultValue;
842
843
538
    int iLine = 0;
844
649k
    for (; papszNV[iLine] != nullptr &&
845
648k
           !EQUALN(papszNV[iLine], pszField, strlen(pszField));
846
648k
         iLine++)
847
648k
    {
848
648k
    }
849
850
538
    if (papszNV[iLine] == nullptr)
851
511
        return pszDefaultValue;
852
27
    else
853
27
    {
854
27
        CPLString osResult;
855
27
        char **papszTokens = CSLTokenizeString(papszNV[iLine]);
856
857
27
        if (CSLCount(papszTokens) > 1)
858
26
            osResult = papszTokens[1];
859
1
        else
860
1
            osResult = pszDefaultValue;
861
862
27
        CSLDestroy(papszTokens);
863
27
        return osResult;
864
27
    }
865
538
}
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
896
0
    GDALDatasetH hDS = GDALOpen(osOldPath, GA_ReadOnly);
897
0
    if (hDS == nullptr)
898
0
        return CE_Failure;
899
900
0
    char **papszFileList = GDALGetFileList(hDS);
901
0
    GDALClose(hDS);
902
903
0
    if (papszFileList == nullptr)
904
0
        return CE_Failure;
905
906
    /* -------------------------------------------------------------------- */
907
    /*      Work out the corresponding new names.                           */
908
    /* -------------------------------------------------------------------- */
909
0
    char **papszNewFileList = nullptr;
910
911
0
    for (int i = 0; papszFileList[i] != nullptr; i++)
912
0
    {
913
0
        CPLString osNewFilename;
914
915
0
        if (!EQUALN(papszFileList[i], osOldPath, osOldPath.size()))
916
0
        {
917
0
            CPLAssert(false);
918
0
            return CE_Failure;
919
0
        }
920
921
0
        osNewFilename = osNewPath + (papszFileList[i] + osOldPath.size());
922
923
0
        papszNewFileList = CSLAddString(papszNewFileList, 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
            CSLDestroy(papszNewFileList);
937
0
            return CE_Failure;
938
0
        }
939
0
    }
940
941
    /* -------------------------------------------------------------------- */
942
    /*      Copy/rename any remaining files.                                */
943
    /* -------------------------------------------------------------------- */
944
0
    VSIStatBufL sStatBuf;
945
946
0
    for (int i = 0; papszFileList[i] != nullptr; i++)
947
0
    {
948
0
        if (VSIStatL(papszFileList[i], &sStatBuf) == 0 &&
949
0
            VSI_ISREG(sStatBuf.st_mode))
950
0
        {
951
0
            if (CPLMoveFile(papszNewFileList[i], papszFileList[i]) != 0)
952
0
            {
953
0
                CPLError(CE_Failure, CPLE_AppDefined,
954
0
                         "Unable to move %s to %s:\n%s", papszFileList[i],
955
0
                         papszNewFileList[i], VSIStrerror(errno));
956
0
                CSLDestroy(papszNewFileList);
957
0
                return CE_Failure;
958
0
            }
959
0
        }
960
0
    }
961
962
0
    if (VSIStatL(osOldPath, &sStatBuf) == 0)
963
0
    {
964
0
        if (CPLUnlinkTree(osOldPath) != 0)
965
0
        {
966
0
            CPLError(CE_Warning, CPLE_AppDefined,
967
0
                     "Unable to cleanup old path.");
968
0
        }
969
0
    }
970
971
0
    CSLDestroy(papszFileList);
972
0
    CSLDestroy(papszNewFileList);
973
0
    return CE_None;
974
0
}
975
976
/************************************************************************/
977
/*                             AIGDelete()                              */
978
/*                                                                      */
979
/*      Custom dataset deleter for AIG dataset.                         */
980
/************************************************************************/
981
982
static CPLErr AIGDelete(const char *pszDatasetname)
983
984
0
{
985
    /* -------------------------------------------------------------------- */
986
    /*      Get file list.                                                  */
987
    /* -------------------------------------------------------------------- */
988
0
    GDALDatasetH hDS = GDALOpen(pszDatasetname, GA_ReadOnly);
989
0
    if (hDS == nullptr)
990
0
        return CE_Failure;
991
992
0
    char **papszFileList = GDALGetFileList(hDS);
993
0
    GDALClose(hDS);
994
995
0
    if (papszFileList == nullptr)
996
0
        return CE_Failure;
997
998
    /* -------------------------------------------------------------------- */
999
    /*      Delete all regular files.                                       */
1000
    /* -------------------------------------------------------------------- */
1001
0
    for (int i = 0; papszFileList[i] != nullptr; i++)
1002
0
    {
1003
0
        VSIStatBufL sStatBuf;
1004
0
        if (VSIStatL(papszFileList[i], &sStatBuf) == 0 &&
1005
0
            VSI_ISREG(sStatBuf.st_mode))
1006
0
        {
1007
0
            if (VSIUnlink(papszFileList[i]) != 0)
1008
0
            {
1009
0
                CPLError(CE_Failure, CPLE_AppDefined,
1010
0
                         "Unable to delete '%s':\n%s", papszFileList[i],
1011
0
                         VSIStrerror(errno));
1012
0
                return CE_Failure;
1013
0
            }
1014
0
        }
1015
0
    }
1016
1017
    /* -------------------------------------------------------------------- */
1018
    /*      Delete directories.                                             */
1019
    /* -------------------------------------------------------------------- */
1020
0
    for (int i = 0; papszFileList[i] != nullptr; i++)
1021
0
    {
1022
0
        VSIStatBufL sStatBuf;
1023
0
        if (VSIStatL(papszFileList[i], &sStatBuf) == 0 &&
1024
0
            VSI_ISDIR(sStatBuf.st_mode))
1025
0
        {
1026
0
            if (CPLUnlinkTree(papszFileList[i]) != 0)
1027
0
                return CE_Failure;
1028
0
        }
1029
0
    }
1030
1031
0
    return CE_None;
1032
0
}
1033
1034
/************************************************************************/
1035
/*                          GDALRegister_AIG()                          */
1036
/************************************************************************/
1037
1038
void GDALRegister_AIGrid()
1039
1040
24
{
1041
24
    if (GDALGetDriverByName("AIG") != nullptr)
1042
0
        return;
1043
1044
24
    GDALDriver *poDriver = new GDALDriver();
1045
1046
24
    poDriver->SetDescription("AIG");
1047
24
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1048
24
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Arc/Info Binary Grid");
1049
24
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/aig.html");
1050
24
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1051
1052
24
    poDriver->pfnOpen = AIGDataset::Open;
1053
1054
24
    poDriver->pfnRename = AIGRename;
1055
24
    poDriver->pfnDelete = AIGDelete;
1056
1057
24
    GetGDALDriverManager()->RegisterDriver(poDriver);
1058
24
}