Coverage Report

Created: 2026-09-26 08:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/northwood/grcdataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GRC Reader
4
 * Purpose:  GDAL driver for Northwood Classified Format
5
 * Author:   Perry Casson
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2007, Waypoint Information Technology
9
 * Copyright (c) 2009-2012, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "gdal_frmts.h"
15
#include "gdal_pam.h"
16
#include "gdal_colortable.h"
17
#include "gdal_driver.h"
18
#include "gdal_drivermanager.h"
19
#include "gdal_openinfo.h"
20
#include "gdal_cpp_functions.h"
21
#include "northwood.h"
22
#include "ogrmitabspatialref.h"
23
24
/************************************************************************/
25
/* ==================================================================== */
26
/*                             NWT_GRCDataset                           */
27
/* ==================================================================== */
28
/************************************************************************/
29
class NWT_GRCRasterBand;
30
31
class NWT_GRCDataset final : public GDALPamDataset
32
{
33
    friend class NWT_GRCRasterBand;
34
35
  private:
36
    VSILFILE *fp;
37
    GByte abyHeader[1024];
38
    NWT_GRID *pGrd;
39
    char **papszCategories;
40
    mutable OGRSpatialReference m_oSRS{};
41
42
    NWT_GRCDataset(const NWT_GRCDataset &) = delete;
43
    NWT_GRCDataset &operator=(const NWT_GRCDataset &) = delete;
44
45
  protected:
46
    GDALColorTable *poColorTable;
47
48
  public:
49
    NWT_GRCDataset();
50
    ~NWT_GRCDataset() override;
51
52
    static GDALDataset *Open(GDALOpenInfo *);
53
    static int Identify(GDALOpenInfo *poOpenInfo);
54
55
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
56
    const OGRSpatialReference *GetSpatialRef() const override;
57
};
58
59
/************************************************************************/
60
/* ==================================================================== */
61
/*                            NWT_GRCRasterBand                         */
62
/* ==================================================================== */
63
/************************************************************************/
64
65
class NWT_GRCRasterBand final : public GDALPamRasterBand
66
{
67
    friend class NWT_GRCDataset;
68
69
  public:
70
    NWT_GRCRasterBand(NWT_GRCDataset *, int);
71
    ~NWT_GRCRasterBand() override;
72
73
    CPLErr IReadBlock(int, int, void *) override;
74
    double GetNoDataValue(int *pbSuccess) override;
75
76
    GDALColorInterp GetColorInterpretation() override;
77
    char **GetCategoryNames() override;
78
    GDALColorTable *GetColorTable() override;
79
};
80
81
/************************************************************************/
82
/*                         NWT_GRCRasterBand()                          */
83
/************************************************************************/
84
85
NWT_GRCRasterBand::NWT_GRCRasterBand(NWT_GRCDataset *poDSIn, int nBandIn)
86
2
{
87
2
    poDS = poDSIn;
88
2
    nBand = nBandIn;
89
2
    NWT_GRCDataset *poGDS = cpl::down_cast<NWT_GRCDataset *>(poDS);
90
91
2
    if (poGDS->pGrd->nBitsPerPixel == 8)
92
2
        eDataType = GDT_UInt8;
93
0
    else if (poGDS->pGrd->nBitsPerPixel == 16)
94
0
        eDataType = GDT_UInt16;
95
0
    else                         /* if( poGDS->pGrd->nBitsPerPixel == 32 ) */
96
0
        eDataType = GDT_UInt32;  // this would be funny
97
98
2
    nBlockXSize = poDS->GetRasterXSize();
99
2
    nBlockYSize = 1;
100
101
    // load the color table and might as well to the ClassNames
102
2
    poGDS->poColorTable = new GDALColorTable();
103
104
2
    GDALColorEntry oEntry = {255, 255, 255, 0};
105
    // null value = 0 is transparent
106
    // alpha 0 = transparent
107
108
2
    poGDS->poColorTable->SetColorEntry(0, &oEntry);
109
110
2
    for (int i = 0;
111
8
         i < static_cast<int>(poGDS->pGrd->stClassDict->nNumClassifiedItems);
112
6
         i++)
113
6
    {
114
6
        oEntry.c1 = poGDS->pGrd->stClassDict->stClassifiedItem[i]->r;
115
6
        oEntry.c2 = poGDS->pGrd->stClassDict->stClassifiedItem[i]->g;
116
6
        oEntry.c3 = poGDS->pGrd->stClassDict->stClassifiedItem[i]->b;
117
6
        oEntry.c4 = 255;  // alpha 255 = solid
118
119
6
        poGDS->poColorTable->SetColorEntry(
120
6
            poGDS->pGrd->stClassDict->stClassifiedItem[i]->usPixVal, &oEntry);
121
6
    }
122
123
    // find the max value used in the grc
124
2
    int maxValue = 0;
125
2
    for (int i = 0;
126
8
         i < static_cast<int>(poGDS->pGrd->stClassDict->nNumClassifiedItems);
127
6
         i++)
128
6
    {
129
6
        if (poGDS->pGrd->stClassDict->stClassifiedItem[i]->usPixVal > maxValue)
130
6
            maxValue = poGDS->pGrd->stClassDict->stClassifiedItem[i]->usPixVal;
131
6
    }
132
133
    // load a value for the null value
134
2
    poGDS->papszCategories = CSLAddString(poGDS->papszCategories, "No Data");
135
136
    // for the class names we need to load nulls string for all classes that
137
    // are not defined
138
8
    for (int val = 1; val <= maxValue; val++)
139
6
    {
140
6
        int i = 0;
141
        // Loop through the GRC dictionary to see if the value is defined.
142
12
        for (; i <
143
12
               static_cast<int>(poGDS->pGrd->stClassDict->nNumClassifiedItems);
144
6
             i++)
145
12
        {
146
12
            if (static_cast<int>(
147
12
                    poGDS->pGrd->stClassDict->stClassifiedItem[i]->usPixVal) ==
148
12
                val)
149
6
            {
150
6
                poGDS->papszCategories = CSLAddString(
151
6
                    poGDS->papszCategories,
152
6
                    poGDS->pGrd->stClassDict->stClassifiedItem[i]->szClassName);
153
6
                break;
154
6
            }
155
12
        }
156
6
        if (i >=
157
6
            static_cast<int>(poGDS->pGrd->stClassDict->nNumClassifiedItems))
158
0
            poGDS->papszCategories = CSLAddString(poGDS->papszCategories, "");
159
6
    }
160
2
}
161
162
NWT_GRCRasterBand::~NWT_GRCRasterBand()
163
2
{
164
2
}
165
166
double NWT_GRCRasterBand::GetNoDataValue(int *pbSuccess)
167
8
{
168
8
    if (pbSuccess != nullptr)
169
6
        *pbSuccess = TRUE;
170
171
8
    return 0.0;  // Northwood grid 0 is always null.
172
8
}
173
174
// return an array of null terminated strings for the class names
175
char **NWT_GRCRasterBand::GetCategoryNames()
176
0
{
177
0
    NWT_GRCDataset *poGDS = cpl::down_cast<NWT_GRCDataset *>(poDS);
178
179
0
    return poGDS->papszCategories;
180
0
}
181
182
// return the color table
183
GDALColorTable *NWT_GRCRasterBand::GetColorTable()
184
0
{
185
0
    NWT_GRCDataset *poGDS = cpl::down_cast<NWT_GRCDataset *>(poDS);
186
187
0
    return poGDS->poColorTable;
188
0
}
189
190
GDALColorInterp NWT_GRCRasterBand::GetColorInterpretation()
191
0
{
192
0
    if (nBand == 1)
193
0
        return GCI_PaletteIndex;
194
195
0
    return GCI_Undefined;
196
0
}
197
198
/************************************************************************/
199
/*                             IReadBlock()                             */
200
/************************************************************************/
201
CPLErr NWT_GRCRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
202
                                     void *pImage)
203
362
{
204
362
    NWT_GRCDataset *poGDS = cpl::down_cast<NWT_GRCDataset *>(poDS);
205
362
    const int nBytesPerPixel = poGDS->pGrd->nBitsPerPixel / 8;
206
362
    if (nBytesPerPixel <= 0 || nBlockXSize > INT_MAX / nBytesPerPixel)
207
0
        return CE_Failure;
208
362
    const int nRecordSize = nBlockXSize * nBytesPerPixel;
209
210
362
    if (nBand == 1)
211
362
    {  // grc's are just one band of indices
212
362
        VSIFSeekL(poGDS->fp,
213
362
                  1024 + nRecordSize * static_cast<vsi_l_offset>(nBlockYOff),
214
362
                  SEEK_SET);
215
362
        if (static_cast<int>(VSIFReadL(pImage, 1, nRecordSize, poGDS->fp)) !=
216
362
            nRecordSize)
217
0
            return CE_Failure;
218
362
    }
219
0
    else
220
0
    {
221
0
        CPLError(CE_Failure, CPLE_IllegalArg, "No band number %d", nBand);
222
0
        return CE_Failure;
223
0
    }
224
362
    return CE_None;
225
362
}
226
227
/************************************************************************/
228
/* ==================================================================== */
229
/*                          NWT_GRCDataset                              */
230
/* ==================================================================== */
231
/************************************************************************/
232
NWT_GRCDataset::NWT_GRCDataset()
233
8
    : fp(nullptr), pGrd(nullptr), papszCategories(nullptr),
234
8
      poColorTable(nullptr)
235
8
{
236
8
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
237
8
    memset(abyHeader, 0, sizeof(abyHeader));
238
8
}
239
240
/************************************************************************/
241
/*                          ~NWT_GRCDataset()                           */
242
/************************************************************************/
243
NWT_GRCDataset::~NWT_GRCDataset()
244
8
{
245
8
    delete poColorTable;
246
8
    CSLDestroy(papszCategories);
247
248
8
    NWT_GRCDataset::FlushCache(true);
249
8
    if (pGrd)
250
8
    {
251
8
        pGrd->fp = nullptr;  // this prevents nwtCloseGrid from closing the fp
252
8
        nwtCloseGrid(pGrd);
253
8
    }
254
255
8
    if (fp != nullptr)
256
8
        VSIFCloseL(fp);
257
8
}
258
259
/************************************************************************/
260
/*                          GetGeoTransform()                           */
261
/************************************************************************/
262
CPLErr NWT_GRCDataset::GetGeoTransform(GDALGeoTransform &gt) const
263
2
{
264
2
    gt.xorig = pGrd->dfMinX - (pGrd->dfStepSize * 0.5);
265
2
    gt.yorig = pGrd->dfMaxY + (pGrd->dfStepSize * 0.5);
266
2
    gt.xscale = pGrd->dfStepSize;
267
2
    gt.xrot = 0.0;
268
269
2
    gt.yrot = 0.0;
270
2
    gt.yscale = -1 * pGrd->dfStepSize;
271
272
2
    return CE_None;
273
2
}
274
275
/************************************************************************/
276
/*                           GetSpatialRef()                            */
277
/************************************************************************/
278
const OGRSpatialReference *NWT_GRCDataset::GetSpatialRef() const
279
2
{
280
2
    if (m_oSRS.IsEmpty())
281
2
    {
282
2
        OGRSpatialReference *poSpatialRef =
283
2
            MITABCoordSys2SpatialRef(pGrd->cMICoordSys);
284
2
        if (poSpatialRef)
285
2
        {
286
2
            m_oSRS = *poSpatialRef;
287
2
            m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
288
2
            poSpatialRef->Release();
289
2
        }
290
2
    }
291
2
    return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
292
2
}
293
294
/************************************************************************/
295
/*                              Identify()                              */
296
/************************************************************************/
297
298
int NWT_GRCDataset::Identify(GDALOpenInfo *poOpenInfo)
299
487k
{
300
    /* -------------------------------------------------------------------- */
301
    /*  Look for the header                                                 */
302
    /* -------------------------------------------------------------------- */
303
487k
    if (poOpenInfo->nHeaderBytes < 1024)
304
429k
        return FALSE;
305
306
58.3k
    if (poOpenInfo->pabyHeader[0] != 'H' || poOpenInfo->pabyHeader[1] != 'G' ||
307
17
        poOpenInfo->pabyHeader[2] != 'P' || poOpenInfo->pabyHeader[3] != 'C' ||
308
16
        poOpenInfo->pabyHeader[4] != '8')
309
58.2k
        return FALSE;
310
311
16
    return TRUE;
312
58.3k
}
313
314
/************************************************************************/
315
/*                                Open()                                */
316
/************************************************************************/
317
318
GDALDataset *NWT_GRCDataset::Open(GDALOpenInfo *poOpenInfo)
319
8
{
320
8
    if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
321
0
        return nullptr;
322
323
    /* -------------------------------------------------------------------- */
324
    /*      Create a corresponding GDALDataset.                             */
325
    /* -------------------------------------------------------------------- */
326
8
    NWT_GRCDataset *poDS = new NWT_GRCDataset();
327
328
8
    poDS->fp = poOpenInfo->fpL;
329
8
    poOpenInfo->fpL = nullptr;
330
331
    /* -------------------------------------------------------------------- */
332
    /*      Read the header.                                                */
333
    /* -------------------------------------------------------------------- */
334
8
    VSIFSeekL(poDS->fp, 0, SEEK_SET);
335
8
    VSIFReadL(poDS->abyHeader, 1, 1024, poDS->fp);
336
8
    poDS->pGrd = static_cast<NWT_GRID *>(malloc(sizeof(NWT_GRID)));
337
8
    if (!poDS->pGrd)
338
0
    {
339
0
        delete poDS;
340
0
        return nullptr;
341
0
    }
342
343
8
    poDS->pGrd->fp = poDS->fp;
344
345
8
    if (!nwt_ParseHeader(poDS->pGrd, poDS->abyHeader) ||
346
4
        !GDALCheckDatasetDimensions(poDS->pGrd->nXSide, poDS->pGrd->nYSide) ||
347
4
        poDS->pGrd->stClassDict == nullptr)
348
5
    {
349
5
        delete poDS;
350
5
        return nullptr;
351
5
    }
352
353
3
    if (poDS->pGrd->nBitsPerPixel != 8 && poDS->pGrd->nBitsPerPixel != 16 &&
354
1
        poDS->pGrd->nBitsPerPixel != 32)
355
1
    {
356
1
        delete poDS;
357
1
        return nullptr;
358
1
    }
359
360
2
    poDS->nRasterXSize = poDS->pGrd->nXSide;
361
2
    poDS->nRasterYSize = poDS->pGrd->nYSide;
362
363
    /* -------------------------------------------------------------------- */
364
    /*      Create band information objects.                                */
365
    /* -------------------------------------------------------------------- */
366
2
    poDS->SetBand(1, new NWT_GRCRasterBand(poDS, 1));  // Class Indexes
367
368
    /* -------------------------------------------------------------------- */
369
    /*      Initialize any PAM information.                                 */
370
    /* -------------------------------------------------------------------- */
371
2
    poDS->SetDescription(poOpenInfo->pszFilename);
372
2
    poDS->TryLoadXML();
373
374
    /* -------------------------------------------------------------------- */
375
    /*      Check for external overviews.                                   */
376
    /* -------------------------------------------------------------------- */
377
2
    poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename,
378
2
                                poOpenInfo->GetSiblingFiles());
379
380
2
    return poDS;
381
3
}
382
383
/************************************************************************/
384
/*                          GDALRegister_GRC()                          */
385
/************************************************************************/
386
387
void GDALRegister_NWT_GRC()
388
389
24
{
390
24
    if (GDALGetDriverByName("NWT_GRC") != nullptr)
391
0
        return;
392
393
24
    GDALDriver *poDriver = new GDALDriver();
394
395
24
    poDriver->SetDescription("NWT_GRC");
396
24
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
397
24
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
398
24
                              "Northwood Classified Grid Format .grc/.tab");
399
24
    poDriver->SetMetadataItem(
400
24
        GDAL_DMD_HELPTOPIC,
401
24
        "drivers/raster/nwtgrd.html#driver-capabilities-nwt-grc");
402
24
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "grc");
403
24
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
404
405
24
    poDriver->pfnOpen = NWT_GRCDataset::Open;
406
24
    poDriver->pfnIdentify = NWT_GRCDataset::Identify;
407
408
24
    GetGDALDriverManager()->RegisterDriver(poDriver);
409
24
}