Coverage Report

Created: 2026-05-16 08:20

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/northwood/grddataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GRD Reader
4
 * Purpose:  GDAL driver for Northwood Grid Format
5
 * Author:   Perry Casson
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2006, 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 <string>
15
#include <cstring>
16
#include <cstdio>
17
#include <climits>
18
#include "gdal_frmts.h"
19
#include "gdal_pam.h"
20
#include "gdal_driver.h"
21
#include "gdal_drivermanager.h"
22
#include "gdal_openinfo.h"
23
#include "gdal_cpp_functions.h"
24
#include "northwood.h"
25
#include "ogrmitabspatialref.h"
26
27
#ifndef NO_MITAB_SUPPORT
28
#ifdef MSVC
29
#include "..\..\ogr\ogrsf_frmts\mitab\mitab.h"
30
#else
31
#include "../../ogr/ogrsf_frmts/mitab/mitab.h"
32
#endif
33
#endif
34
35
constexpr float NODATA = -1.e37f;
36
constexpr double SCALE16BIT = 65534.0;
37
constexpr double SCALE32BIT = 4294967294.0;
38
39
void replaceExt(std::string &s, const std::string &newExt);
40
41
/************************************************************************/
42
/*  Replace the extension on a filepath with an alternative extension   */
43
/************************************************************************/
44
void replaceExt(std::string &s, const std::string &newExt)
45
0
{
46
47
0
    std::string::size_type i = s.rfind('.', s.length());
48
49
0
    if (i != std::string::npos)
50
0
    {
51
0
        s.replace(i + 1, newExt.length(), newExt);
52
0
    }
53
0
}
54
55
/************************************************************************/
56
/* ==================================================================== */
57
/*                      NWT_GRDDataset                                  */
58
/* ==================================================================== */
59
/************************************************************************/
60
class NWT_GRDRasterBand;
61
62
class NWT_GRDDataset final : public GDALPamDataset
63
{
64
    friend class NWT_GRDRasterBand;
65
66
    VSILFILE *fp;
67
    GByte abyHeader[1024];
68
    NWT_GRID *pGrd;
69
    NWT_RGB ColorMap[4096];
70
    bool bUpdateHeader;
71
    mutable OGRSpatialReference *m_poSRS = nullptr;
72
73
#ifndef NO_MITAB_SUPPORT
74
    // Update the header data with latest changes
75
    int UpdateHeader();
76
    int WriteTab();
77
#endif
78
79
    NWT_GRDDataset(const NWT_GRDDataset &) = delete;
80
    NWT_GRDDataset &operator=(const NWT_GRDDataset &) = delete;
81
82
  public:
83
    NWT_GRDDataset();
84
    ~NWT_GRDDataset() override;
85
86
    static GDALDataset *Open(GDALOpenInfo *);
87
    static int Identify(GDALOpenInfo *);
88
89
#ifndef NO_MITAB_SUPPORT
90
    static GDALDataset *Create(const char *pszFilename, int nXSize, int nYSize,
91
                               int nBandsIn, GDALDataType eType,
92
                               CSLConstList papszParamList);
93
    static GDALDataset *CreateCopy(const char *pszFilename,
94
                                   GDALDataset *poSrcDS, int bStrict,
95
                                   CSLConstList papszOptions,
96
                                   GDALProgressFunc pfnProgress,
97
                                   void *pProgressData);
98
#endif
99
100
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
101
    CPLErr SetGeoTransform(const GDALGeoTransform &gt) override;
102
    CPLErr FlushCache(bool bAtClosing) override;
103
104
    const OGRSpatialReference *GetSpatialRef() const override;
105
106
#ifndef NO_MITAB_SUPPORT
107
    CPLErr SetSpatialRef(const OGRSpatialReference *poSRS) override;
108
#endif
109
};
110
111
/************************************************************************/
112
/* ==================================================================== */
113
/*                            NWT_GRDRasterBand                         */
114
/* ==================================================================== */
115
/************************************************************************/
116
117
class NWT_GRDRasterBand final : public GDALPamRasterBand
118
{
119
    friend class NWT_GRDDataset;
120
121
    int bHaveOffsetScale;
122
    double dfOffset;
123
    double dfScale;
124
    double dfNoData;
125
126
  public:
127
    NWT_GRDRasterBand(NWT_GRDDataset *, int, int);
128
129
    CPLErr IReadBlock(int, int, void *) override;
130
    CPLErr IWriteBlock(int, int, void *) override;
131
    double GetNoDataValue(int *pbSuccess) override;
132
    CPLErr SetNoDataValue(double dfNoData) override;
133
134
    GDALColorInterp GetColorInterpretation() override;
135
};
136
137
/************************************************************************/
138
/*                         NWT_GRDRasterBand()                          */
139
/************************************************************************/
140
NWT_GRDRasterBand::NWT_GRDRasterBand(NWT_GRDDataset *poDSIn, int nBandIn,
141
                                     int nBands)
142
32
    : bHaveOffsetScale(FALSE), dfOffset(0.0), dfScale(1.0), dfNoData(0.0)
143
32
{
144
32
    poDS = poDSIn;
145
32
    nBand = nBandIn;
146
147
    /*
148
     * If nBand = 4 we have opened in read mode and have created the 3 'virtual'
149
     * RGB bands. so the 4th band is the actual data Otherwise, if we have
150
     * opened in update mode, there is only 1 band, which is the actual data
151
     */
152
32
    if (nBand == 4 || nBands == 1)
153
8
    {
154
8
        bHaveOffsetScale = TRUE;
155
8
        dfOffset = poDSIn->pGrd->fZMin;
156
157
8
        if (poDSIn->pGrd->cFormat == 0x00)
158
4
        {
159
4
            eDataType = GDT_Float32;
160
4
            dfScale = (poDSIn->pGrd->fZMax - poDSIn->pGrd->fZMin) / SCALE16BIT;
161
4
        }
162
4
        else
163
4
        {
164
4
            eDataType = GDT_Float32;
165
4
            dfScale = (poDSIn->pGrd->fZMax - poDSIn->pGrd->fZMin) / SCALE32BIT;
166
4
        }
167
8
    }
168
24
    else
169
24
    {
170
24
        bHaveOffsetScale = FALSE;
171
24
        dfOffset = 0;
172
24
        dfScale = 1.0;
173
24
        eDataType = GDT_UInt8;
174
24
    }
175
32
    nBlockXSize = poDS->GetRasterXSize();
176
32
    nBlockYSize = 1;
177
32
}
178
179
double NWT_GRDRasterBand::GetNoDataValue(int *pbSuccess)
180
201
{
181
201
    NWT_GRDDataset *poGDS = cpl::down_cast<NWT_GRDDataset *>(poDS);
182
201
    double dRetval;
183
201
    if ((nBand == 4) || (poGDS->nBands == 1))
184
177
    {
185
177
        if (pbSuccess != nullptr)
186
177
            *pbSuccess = TRUE;
187
177
        if (dfNoData != 0.0)
188
0
        {
189
0
            dRetval = dfNoData;
190
0
        }
191
177
        else
192
177
        {
193
177
            dRetval = NODATA;
194
177
        }
195
196
177
        return dRetval;
197
177
    }
198
199
24
    if (pbSuccess != nullptr)
200
24
        *pbSuccess = FALSE;
201
202
24
    return 0;
203
201
}
204
205
CPLErr NWT_GRDRasterBand::SetNoDataValue(double dfNoDataIn)
206
0
{
207
    // This is essentially a 'virtual' no data value.
208
    // Once set, when writing an value == dfNoData will
209
    // be converted to the no data value (0) on disk.
210
    // If opened again; the no data value will always be the
211
    // default (-1.e37f)
212
0
    dfNoData = dfNoDataIn;
213
0
    return CE_None;
214
0
}
215
216
GDALColorInterp NWT_GRDRasterBand::GetColorInterpretation()
217
8
{
218
8
    NWT_GRDDataset *poGDS = cpl::down_cast<NWT_GRDDataset *>(poDS);
219
    // return GCI_RGB;
220
8
    if ((nBand == 4) || (poGDS->nBands == 1))
221
8
        return GCI_GrayIndex;
222
0
    else if (nBand == 1)
223
0
        return GCI_RedBand;
224
0
    else if (nBand == 2)
225
0
        return GCI_GreenBand;
226
0
    else if (nBand == 3)
227
0
        return GCI_BlueBand;
228
229
0
    return GCI_Undefined;
230
8
}
231
232
/************************************************************************/
233
/*                            IWriteBlock()                             */
234
/************************************************************************/
235
CPLErr NWT_GRDRasterBand::IWriteBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
236
                                      void *pImage)
237
0
{
238
239
    // Each block is an entire row of the dataset, so the x offset should always
240
    // be 0
241
0
    CPLAssert(nBlockXOff == 0);
242
0
    NWT_GRDDataset *poGDS = cpl::down_cast<NWT_GRDDataset *>(poDS);
243
244
0
    if (dfScale == 0.0)
245
0
        return CE_Failure;
246
247
    // Ensure the blocksize is not beyond the system limits and
248
    // initialize the size of the record
249
0
    if (nBlockXSize > INT_MAX / 2)
250
0
    {
251
0
        return CE_Failure;
252
0
    }
253
0
    const int nRecordSize = nBlockXSize * 2;
254
255
    // Seek to the write position in the GRD file
256
0
    VSIFSeekL(poGDS->fp,
257
0
              1024 + nRecordSize * static_cast<vsi_l_offset>(nBlockYOff),
258
0
              SEEK_SET);
259
260
    // Cast pImage to float
261
0
    const float *pfImage = static_cast<const float *>(pImage);
262
263
    // Initialize output array
264
0
    GByte *pabyRecord = static_cast<GByte *>(VSI_MALLOC_VERBOSE(nRecordSize));
265
0
    if (pabyRecord == nullptr)
266
0
        return CE_Failure;
267
268
    // We only ever write to band 4; RGB bands are basically 'virtual'
269
    // (i.e. the RGB colour is computed from the raw data).
270
    // For all intents and purposes, there is essentially 1 band on disk.
271
0
    if (nBand == 1)
272
0
    {
273
0
        for (int i = 0; i < nBlockXSize; i++)
274
0
        {
275
0
            const float fValue = pfImage[i];
276
0
            unsigned short nWrite;  // The stretched value to be written
277
278
            // We allow data to be interpreted by a user-defined null value
279
            // (a 'virtual' value, since it is always 0 on disk) or
280
            // if not defined we default to the GRD standard of -1E37.
281
            // We allow a little bit of flexibility in that if it is below -1E37
282
            // it is in all probability still intended as a null value.
283
0
            if ((fValue == dfNoData) || (fValue <= NODATA))
284
0
            {
285
0
                nWrite = 0;
286
0
            }
287
0
            else
288
0
            {
289
0
                if (fValue < poGDS->pGrd->fZMin)
290
0
                {
291
0
                    poGDS->pGrd->fZMin = fValue;
292
0
                }
293
0
                else if (fValue > poGDS->pGrd->fZMax)
294
0
                {
295
0
                    poGDS->pGrd->fZMax = fValue;
296
0
                }
297
                // Data on disk is stretched within the unsigned short range so
298
                // we must convert (the inverse of what is done in IReadBlock),
299
                // based on the Z value range
300
0
                nWrite = static_cast<unsigned short>(
301
0
                    ((fValue - dfOffset) / dfScale) + 1);
302
0
            }
303
0
            CPL_LSBPTR16(&nWrite);
304
            // Copy the result to the byte array (2 bytes per value)
305
0
            memcpy(pabyRecord + 2 * i, &nWrite, 2);
306
0
        }
307
308
        // Write the buffer to disk
309
0
        if (VSIFWriteL(pabyRecord, 1, nRecordSize, poGDS->fp) !=
310
0
            static_cast<size_t>(nRecordSize))
311
0
        {
312
0
            CPLError(CE_Failure, CPLE_FileIO,
313
0
                     "Failed to write scanline %d to file.\n", nBlockYOff);
314
0
            CPLFree(pabyRecord);
315
0
            return CE_Failure;
316
0
        }
317
0
    }
318
0
    else
319
0
    {
320
0
        CPLError(CE_Failure, CPLE_IllegalArg, "Writing to band %d is not valid",
321
0
                 nBand);
322
0
        CPLFree(pabyRecord);
323
0
        return CE_Failure;
324
0
    }
325
0
    CPLFree(pabyRecord);
326
0
    return CE_None;
327
0
}
328
329
/************************************************************************/
330
/*                             IReadBlock()                             */
331
/************************************************************************/
332
CPLErr NWT_GRDRasterBand::IReadBlock(CPL_UNUSED int nBlockXOff, int nBlockYOff,
333
                                     void *pImage)
334
736
{
335
736
    NWT_GRDDataset *poGDS = cpl::down_cast<NWT_GRDDataset *>(poDS);
336
736
    if (nBlockXSize > INT_MAX / 2)
337
0
        return CE_Failure;
338
736
    const int nRecordSize = nBlockXSize * 2;
339
340
    // Seek to the data position
341
736
    VSIFSeekL(poGDS->fp,
342
736
              1024 + nRecordSize * static_cast<vsi_l_offset>(nBlockYOff),
343
736
              SEEK_SET);
344
345
736
    GByte *pabyRecord = static_cast<GByte *>(VSI_MALLOC_VERBOSE(nRecordSize));
346
736
    if (pabyRecord == nullptr)
347
0
        return CE_Failure;
348
349
    // Read the data
350
736
    if (static_cast<int>(VSIFReadL(pabyRecord, 1, nRecordSize, poGDS->fp)) !=
351
736
        nRecordSize)
352
28
    {
353
28
        CPLFree(pabyRecord);
354
28
        return CE_Failure;
355
28
    }
356
357
708
    if ((nBand == 4) || (poGDS->nBands == 1))  // Z values
358
177
    {
359
177
        int bSuccess;
360
177
        const float fNoData = static_cast<float>(GetNoDataValue(&bSuccess));
361
11.3k
        for (int i = 0; i < nBlockXSize; i++)
362
11.1k
        {
363
11.1k
            unsigned short raw1;
364
11.1k
            memcpy(&raw1, pabyRecord + 2 * i, 2);
365
11.1k
            CPL_LSBPTR16(&raw1);
366
11.1k
            if (raw1 == 0)
367
2.08k
            {
368
2.08k
                static_cast<float *>(pImage)[i] = fNoData;  // null value
369
2.08k
            }
370
9.07k
            else
371
9.07k
            {
372
9.07k
                static_cast<float *>(pImage)[i] =
373
9.07k
                    static_cast<float>(dfOffset + ((raw1 - 1) * dfScale));
374
9.07k
            }
375
11.1k
        }
376
177
    }
377
531
    else if (nBand == 1)  // red values
378
177
    {
379
11.3k
        for (int i = 0; i < nBlockXSize; i++)
380
11.1k
        {
381
11.1k
            unsigned short raw1;
382
11.1k
            memcpy(&raw1, pabyRecord + 2 * i, 2);
383
11.1k
            CPL_LSBPTR16(&raw1);
384
11.1k
            static_cast<unsigned char *>(pImage)[i] =
385
11.1k
                poGDS->ColorMap[raw1 / 16].r;
386
11.1k
        }
387
177
    }
388
354
    else if (nBand == 2)  // green
389
177
    {
390
11.3k
        for (int i = 0; i < nBlockXSize; i++)
391
11.1k
        {
392
11.1k
            unsigned short raw1;
393
11.1k
            memcpy(&raw1, pabyRecord + 2 * i, 2);
394
11.1k
            CPL_LSBPTR16(&raw1);
395
11.1k
            static_cast<unsigned char *>(pImage)[i] =
396
11.1k
                poGDS->ColorMap[raw1 / 16].g;
397
11.1k
        }
398
177
    }
399
177
    else if (nBand == 3)  // blue
400
177
    {
401
11.3k
        for (int i = 0; i < nBlockXSize; i++)
402
11.1k
        {
403
11.1k
            unsigned short raw1;
404
11.1k
            memcpy(&raw1, pabyRecord + 2 * i, 2);
405
11.1k
            CPL_LSBPTR16(&raw1);
406
11.1k
            static_cast<unsigned char *>(pImage)[i] =
407
11.1k
                poGDS->ColorMap[raw1 / 16].b;
408
11.1k
        }
409
177
    }
410
0
    else
411
0
    {
412
0
        CPLError(CE_Failure, CPLE_IllegalArg, "No band number %d", nBand);
413
0
        CPLFree(pabyRecord);
414
0
        return CE_Failure;
415
0
    }
416
417
708
    CPLFree(pabyRecord);
418
419
708
    return CE_None;
420
708
}
421
422
/************************************************************************/
423
/* ==================================================================== */
424
/*                             NWT_GRDDataset                           */
425
/* ==================================================================== */
426
/************************************************************************/
427
428
NWT_GRDDataset::NWT_GRDDataset()
429
8
    : fp(nullptr), pGrd(nullptr), bUpdateHeader(false)
430
8
{
431
    // poCT = NULL;
432
32.7k
    for (size_t i = 0; i < CPL_ARRAYSIZE(ColorMap); ++i)
433
32.7k
    {
434
32.7k
        ColorMap[i].r = 0;
435
32.7k
        ColorMap[i].g = 0;
436
32.7k
        ColorMap[i].b = 0;
437
32.7k
    }
438
8
}
439
440
/************************************************************************/
441
/*                          ~NWT_GRDDataset()                           */
442
/************************************************************************/
443
444
NWT_GRDDataset::~NWT_GRDDataset()
445
8
{
446
447
    // Make sure any changes to the header etc are written
448
    // if we are in update mode.
449
8
    if (eAccess == GA_Update)
450
0
    {
451
0
        NWT_GRDDataset::FlushCache(true);
452
0
    }
453
8
    if (pGrd)
454
8
    {
455
8
        pGrd->fp = nullptr;  // this prevents nwtCloseGrid from closing the fp
456
8
        nwtCloseGrid(pGrd);
457
8
    }
458
8
    if (m_poSRS)
459
7
        m_poSRS->Release();
460
461
8
    if (fp != nullptr)
462
8
        VSIFCloseL(fp);
463
8
}
464
465
/************************************************************************/
466
/*                     ~FlushCache(bool bAtClosing)                     */
467
/************************************************************************/
468
CPLErr NWT_GRDDataset::FlushCache(bool bAtClosing)
469
0
{
470
    // Ensure the header and TAB file are up to date
471
0
    if (bUpdateHeader && pGrd)
472
0
    {
473
0
#ifndef NO_MITAB_SUPPORT
474
0
        UpdateHeader();
475
0
#endif
476
0
    }
477
478
    // Call the parent method
479
0
    return GDALPamDataset::FlushCache(bAtClosing);
480
0
}
481
482
/************************************************************************/
483
/*                          GetGeoTransform()                           */
484
/************************************************************************/
485
486
CPLErr NWT_GRDDataset::GetGeoTransform(GDALGeoTransform &gt) const
487
8
{
488
8
    gt.xorig = pGrd->dfMinX - (pGrd->dfStepSize * 0.5);
489
8
    gt.yorig = pGrd->dfMaxY + (pGrd->dfStepSize * 0.5);
490
8
    gt.xscale = pGrd->dfStepSize;
491
8
    gt.xrot = 0.0;
492
493
8
    gt.yrot = 0.0;
494
8
    gt.yscale = -1 * pGrd->dfStepSize;
495
496
8
    return CE_None;
497
8
}
498
499
/************************************************************************/
500
/*                          SetGeoTransform()                           */
501
/************************************************************************/
502
503
CPLErr NWT_GRDDataset::SetGeoTransform(const GDALGeoTransform &gt)
504
0
{
505
0
    if (gt.xrot != 0.0 || gt.yrot != 0.0)
506
0
    {
507
508
0
        CPLError(CE_Failure, CPLE_NotSupported,
509
0
                 "GRD datasets do not support skew/rotation");
510
0
        return CE_Failure;
511
0
    }
512
0
    pGrd->dfStepSize = gt.xscale;
513
514
    // GRD format sets the min/max coordinates to the centre of the
515
    // cell; We must account for this when copying the GDAL geotransform
516
    // which references the top left corner
517
0
    pGrd->dfMinX = gt.xorig + (pGrd->dfStepSize * 0.5);
518
0
    pGrd->dfMaxY = gt.yorig - (pGrd->dfStepSize * 0.5);
519
520
    // Now set the miny and maxx
521
0
    pGrd->dfMaxX = pGrd->dfMinX + (pGrd->dfStepSize * (nRasterXSize - 1));
522
0
    pGrd->dfMinY = pGrd->dfMaxY - (pGrd->dfStepSize * (nRasterYSize - 1));
523
0
    bUpdateHeader = true;
524
525
0
    return CE_None;
526
0
}
527
528
/************************************************************************/
529
/*                           GetSpatialRef()                            */
530
/************************************************************************/
531
const OGRSpatialReference *NWT_GRDDataset::GetSpatialRef() const
532
8
{
533
534
    // First try getting it from the PAM dataset
535
8
    const OGRSpatialReference *poSRS = GDALPamDataset::GetSpatialRef();
536
8
    if (poSRS)
537
0
        return poSRS;
538
539
8
    if (m_poSRS)
540
0
        return m_poSRS;
541
542
    // If that isn't possible, read it from the GRD file. This may be a less
543
    //  complete projection string.
544
8
    OGRSpatialReference *poSpatialRef =
545
8
        MITABCoordSys2SpatialRef(pGrd->cMICoordSys);
546
8
    m_poSRS = poSpatialRef;
547
548
8
    return m_poSRS;
549
8
}
550
551
#ifndef NO_MITAB_SUPPORT
552
/************************************************************************/
553
/*                           SetSpatialRef()                            */
554
/************************************************************************/
555
556
CPLErr NWT_GRDDataset::SetSpatialRef(const OGRSpatialReference *poSRS)
557
0
{
558
559
0
    char *psTABProj = MITABSpatialRef2CoordSys(poSRS);
560
0
    strncpy(pGrd->cMICoordSys, psTABProj, sizeof(pGrd->cMICoordSys) - 1);
561
0
    pGrd->cMICoordSys[255] = '\0';
562
563
    // Free temp projection.
564
0
    CPLFree(psTABProj);
565
    // Set projection in PAM dataset, so that
566
    // GDAL can always retrieve the complete projection.
567
0
    GDALPamDataset::SetSpatialRef(poSRS);
568
0
    bUpdateHeader = true;
569
570
0
    return CE_None;
571
0
}
572
#endif
573
574
/************************************************************************/
575
/*                              Identify()                              */
576
/************************************************************************/
577
578
int NWT_GRDDataset::Identify(GDALOpenInfo *poOpenInfo)
579
459k
{
580
    /* -------------------------------------------------------------------- */
581
    /*  Look for the header                                                 */
582
    /* -------------------------------------------------------------------- */
583
459k
    if (poOpenInfo->nHeaderBytes < 1024)
584
407k
        return FALSE;
585
586
51.9k
    if (poOpenInfo->pabyHeader[0] != 'H' || poOpenInfo->pabyHeader[1] != 'G' ||
587
25
        poOpenInfo->pabyHeader[2] != 'P' || poOpenInfo->pabyHeader[3] != 'C' ||
588
24
        poOpenInfo->pabyHeader[4] != '1')
589
51.9k
        return FALSE;
590
591
16
    return TRUE;
592
51.9k
}
593
594
/************************************************************************/
595
/*                                Open()                                */
596
/************************************************************************/
597
GDALDataset *NWT_GRDDataset::Open(GDALOpenInfo *poOpenInfo)
598
8
{
599
8
    if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
600
0
        return nullptr;
601
602
    /* -------------------------------------------------------------------- */
603
    /*      Create a corresponding GDALDataset.                             */
604
    /* -------------------------------------------------------------------- */
605
8
    int nBandsToCreate = 0;
606
607
8
    NWT_GRDDataset *poDS = new NWT_GRDDataset();
608
609
8
    poDS->fp = poOpenInfo->fpL;
610
8
    poOpenInfo->fpL = nullptr;
611
612
8
    if (poOpenInfo->eAccess == GA_Update)
613
0
    {
614
0
        nBandsToCreate = 1;
615
0
    }
616
8
    else
617
8
    {
618
8
        nBandsToCreate = atoi(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
619
8
                                                   "BAND_COUNT", "4"));
620
8
        if (nBandsToCreate != 1 && nBandsToCreate != 4)
621
0
        {
622
0
            CPLError(CE_Failure, CPLE_AppDefined, "Wrong value for BAND_COUNT");
623
0
            delete poDS;
624
0
            return nullptr;
625
0
        }
626
8
    }
627
8
    poDS->eAccess = poOpenInfo->eAccess;
628
629
    /* -------------------------------------------------------------------- */
630
    /*      Read the header.                                                */
631
    /* -------------------------------------------------------------------- */
632
8
    VSIFSeekL(poDS->fp, 0, SEEK_SET);
633
8
    VSIFReadL(poDS->abyHeader, 1, 1024, poDS->fp);
634
8
    poDS->pGrd = static_cast<NWT_GRID *>(calloc(1, sizeof(NWT_GRID)));
635
8
    if (!poDS->pGrd)
636
0
    {
637
0
        delete poDS;
638
0
        return nullptr;
639
0
    }
640
641
8
    poDS->pGrd->fp = poDS->fp;
642
643
8
    if (!nwt_ParseHeader(poDS->pGrd, poDS->abyHeader) ||
644
8
        !GDALCheckDatasetDimensions(poDS->pGrd->nXSide, poDS->pGrd->nYSide))
645
0
    {
646
0
        delete poDS;
647
0
        return nullptr;
648
0
    }
649
650
8
    poDS->nRasterXSize = poDS->pGrd->nXSide;
651
8
    poDS->nRasterYSize = poDS->pGrd->nYSide;
652
653
    // create a colorTable
654
    // if( poDS->pGrd->iNumColorInflections > 0 )
655
    //   poDS->CreateColorTable();
656
8
    nwt_LoadColors(poDS->ColorMap, 4096, poDS->pGrd);
657
    /* -------------------------------------------------------------------- */
658
    /*      Create band information objects.                                */
659
    /* If opening in read-only mode, then we create 4 bands (RGBZ)          */
660
    /* with data values being available in band 4. If opening in update mode*/
661
    /* we create 1 band (the data values). This is because in reality, there*/
662
    /* is only 1 band stored on disk. The RGB bands are 'virtual' - derived */
663
    /* from the data values on the fly                                      */
664
    /* -------------------------------------------------------------------- */
665
40
    for (int i = 0; i < nBandsToCreate; ++i)
666
32
    {
667
32
        poDS->SetBand(i + 1,
668
32
                      new NWT_GRDRasterBand(poDS, i + 1, nBandsToCreate));
669
32
    }
670
671
    /* -------------------------------------------------------------------- */
672
    /*      Initialize any PAM information.                                 */
673
    /* -------------------------------------------------------------------- */
674
8
    poDS->SetDescription(poOpenInfo->pszFilename);
675
8
    poDS->TryLoadXML();
676
677
    /* -------------------------------------------------------------------- */
678
    /*      Check for external overviews.                                   */
679
    /* -------------------------------------------------------------------- */
680
8
    poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename,
681
8
                                poOpenInfo->GetSiblingFiles());
682
683
8
    return poDS;
684
8
}
685
686
#ifndef NO_MITAB_SUPPORT
687
/************************************************************************/
688
/*                            UpdateHeader()                            */
689
/************************************************************************/
690
int NWT_GRDDataset::UpdateHeader()
691
0
{
692
0
    int iStatus = 0;
693
0
    TABRawBinBlock *poHeaderBlock = new TABRawBinBlock(TABReadWrite, TRUE);
694
0
    poHeaderBlock->InitNewBlock(fp, 1024);
695
696
    // Write the header string
697
0
    poHeaderBlock->WriteBytes(5, reinterpret_cast<const GByte *>("HGPC1\0"));
698
699
    // Version number
700
0
    poHeaderBlock->WriteFloat(pGrd->fVersion);
701
702
    // Dimensions
703
0
    poHeaderBlock->WriteInt16(static_cast<GInt16>(pGrd->nXSide));
704
0
    poHeaderBlock->WriteInt16(static_cast<GInt16>(pGrd->nYSide));
705
706
    // Extents
707
0
    poHeaderBlock->WriteDouble(pGrd->dfMinX);
708
0
    poHeaderBlock->WriteDouble(pGrd->dfMaxX);
709
0
    poHeaderBlock->WriteDouble(pGrd->dfMinY);
710
0
    poHeaderBlock->WriteDouble(pGrd->dfMaxY);
711
712
    // Z value range
713
0
    poHeaderBlock->WriteFloat(pGrd->fZMin);
714
0
    poHeaderBlock->WriteFloat(pGrd->fZMax);
715
0
    poHeaderBlock->WriteFloat(pGrd->fZMinScale);
716
0
    poHeaderBlock->WriteFloat(pGrd->fZMaxScale);
717
718
    // Description String
719
0
    int nChar = static_cast<int>(strlen(pGrd->cDescription));
720
0
    poHeaderBlock->WriteBytes(
721
0
        nChar, reinterpret_cast<const GByte *>(pGrd->cDescription));
722
0
    poHeaderBlock->WriteZeros(32 - nChar);
723
724
    // Unit Name String
725
0
    nChar = static_cast<int>(strlen(pGrd->cZUnits));
726
0
    poHeaderBlock->WriteBytes(nChar,
727
0
                              reinterpret_cast<const GByte *>(pGrd->cZUnits));
728
0
    poHeaderBlock->WriteZeros(32 - nChar);
729
730
    // Ignore 126 - 141 as unknown usage
731
0
    poHeaderBlock->WriteZeros(15);
732
733
    // Hill shading
734
0
    poHeaderBlock->WriteInt16(pGrd->bHillShadeExists ? 1 : 0);
735
0
    poHeaderBlock->WriteInt16(0);
736
737
0
    poHeaderBlock->WriteByte(pGrd->cHillShadeBrightness);
738
0
    poHeaderBlock->WriteByte(pGrd->cHillShadeContrast);
739
740
    // Ignore 147 - 257 as unknown usage
741
0
    poHeaderBlock->WriteZeros(110);
742
743
    // Write spatial reference
744
0
    poHeaderBlock->WriteBytes(
745
0
        static_cast<int>(strlen(pGrd->cMICoordSys)),
746
0
        reinterpret_cast<const GByte *>(pGrd->cMICoordSys));
747
0
    poHeaderBlock->WriteZeros(256 -
748
0
                              static_cast<int>(strlen(pGrd->cMICoordSys)));
749
750
    // Unit code
751
0
    poHeaderBlock->WriteByte(static_cast<GByte>(pGrd->iZUnits));
752
753
    // Info on shading
754
0
    GByte byDisplayStatus = 0;
755
0
    if (pGrd->bShowHillShade)
756
0
    {
757
0
        byDisplayStatus |= 1 << 6;
758
0
    }
759
0
    if (pGrd->bShowGradient)
760
0
    {
761
0
        byDisplayStatus |= 1 << 7;
762
0
    }
763
764
0
    poHeaderBlock->WriteByte(byDisplayStatus);
765
0
    poHeaderBlock->WriteInt16(0);  // Data Type?
766
767
    // Colour inflections
768
0
    poHeaderBlock->WriteInt16(pGrd->iNumColorInflections);
769
0
    for (int i = 0; i < pGrd->iNumColorInflections; i++)
770
0
    {
771
0
        poHeaderBlock->WriteFloat(pGrd->stInflection[i].zVal);
772
0
        poHeaderBlock->WriteByte(pGrd->stInflection[i].r);
773
0
        poHeaderBlock->WriteByte(pGrd->stInflection[i].g);
774
0
        poHeaderBlock->WriteByte(pGrd->stInflection[i].b);
775
0
    }
776
777
    // Fill in unused blanks
778
0
    poHeaderBlock->WriteZeros((966 - poHeaderBlock->GetCurAddress()));
779
780
    // Azimuth and Inclination
781
0
    poHeaderBlock->WriteFloat(pGrd->fHillShadeAzimuth);
782
0
    poHeaderBlock->WriteFloat(pGrd->fHillShadeAngle);
783
784
    // Write to disk
785
0
    iStatus = poHeaderBlock->CommitToFile();
786
787
0
    delete poHeaderBlock;
788
789
    // Update the TAB file to catch any changes
790
0
    if (WriteTab() != 0)
791
0
        iStatus = -1;
792
793
0
    return iStatus;
794
0
}
795
796
int NWT_GRDDataset::WriteTab()
797
0
{
798
    // Create the filename for the .tab file.
799
0
    const std::string sTabFile(CPLResetExtensionSafe(pGrd->szFileName, "tab"));
800
801
0
    VSILFILE *tabfp = VSIFOpenL(sTabFile.c_str(), "wt");
802
0
    if (tabfp == nullptr)
803
0
    {
804
0
        CPLError(CE_Failure, CPLE_FileIO, "Failed to create file `%s'",
805
0
                 sTabFile.c_str());
806
0
        return -1;
807
0
    }
808
809
0
    bool bOK = true;
810
0
    bOK &= VSIFPrintfL(tabfp, "!table\n") > 0;
811
0
    bOK &= VSIFPrintfL(tabfp, "!version 500\n") > 0;
812
0
    bOK &= VSIFPrintfL(tabfp, "!charset %s\n", "Neutral") > 0;
813
0
    bOK &= VSIFPrintfL(tabfp, "\n") > 0;
814
815
0
    bOK &= VSIFPrintfL(tabfp, "Definition Table\n") > 0;
816
0
    const std::string path(pGrd->szFileName);
817
0
    const std::string basename = path.substr(path.find_last_of("/\\") + 1);
818
0
    bOK &= VSIFPrintfL(tabfp, "  File \"%s\"\n", basename.c_str()) > 0;
819
0
    bOK &= VSIFPrintfL(tabfp, "  Type \"RASTER\"\n") > 0;
820
821
0
    double dMapUnitsPerPixel =
822
0
        (pGrd->dfMaxX - pGrd->dfMinX) / (static_cast<double>(pGrd->nXSide) - 1);
823
0
    double dShift = dMapUnitsPerPixel / 2.0;
824
825
0
    bOK &= VSIFPrintfL(tabfp, "  (%f,%f) (%d,%d) Label \"Pt 1\",\n",
826
0
                       pGrd->dfMinX - dShift, pGrd->dfMaxY + dShift, 0, 0) > 0;
827
0
    bOK &= VSIFPrintfL(tabfp, "  (%f,%f) (%d,%d) Label \"Pt 2\",\n",
828
0
                       pGrd->dfMaxX - dShift, pGrd->dfMinY + dShift,
829
0
                       pGrd->nXSide - 1, pGrd->nYSide - 1) > 0;
830
0
    bOK &= VSIFPrintfL(tabfp, "  (%f,%f) (%d,%d) Label \"Pt 3\"\n",
831
0
                       pGrd->dfMinX - dShift, pGrd->dfMinY + dShift, 0,
832
0
                       pGrd->nYSide - 1) > 0;
833
834
0
    bOK &= VSIFPrintfL(tabfp, "  CoordSys %s\n", pGrd->cMICoordSys) > 0;
835
0
    bOK &= VSIFPrintfL(tabfp, "  Units \"m\"\n") > 0;
836
837
    // Raster Styles.
838
839
    // Raster is a grid, which is style 6.
840
0
    bOK &= VSIFPrintfL(tabfp, "  RasterStyle 6 1\n") > 0;
841
842
    // Brightness - style 1
843
0
    if (pGrd->style.iBrightness > 0)
844
0
    {
845
0
        bOK &= VSIFPrintfL(tabfp, "  RasterStyle 1 %d\n",
846
0
                           pGrd->style.iBrightness) > 0;
847
0
    }
848
849
    // Contrast - style 2
850
0
    if (pGrd->style.iContrast > 0)
851
0
    {
852
0
        bOK &= VSIFPrintfL(tabfp, "  RasterStyle 2 %d\n",
853
0
                           pGrd->style.iContrast) > 0;
854
0
    }
855
856
    // Greyscale - style 3; only need to write if TRUE
857
0
    if (pGrd->style.bGreyscale == TRUE)
858
0
    {
859
0
        bOK &= VSIFPrintfL(tabfp, "  RasterStyle 3 1\n") > 0;
860
0
    }
861
862
    // Flag to render one colour transparent - style 4
863
0
    if (pGrd->style.bTransparent == TRUE)
864
0
    {
865
0
        bOK &= VSIFPrintfL(tabfp, "  RasterStyle 4 1\n") > 0;
866
0
        if (pGrd->style.iTransColour > 0)
867
0
        {
868
0
            bOK &= VSIFPrintfL(tabfp, "  RasterStyle 7 %d\n",
869
0
                               pGrd->style.iTransColour) > 0;
870
0
        }
871
0
    }
872
873
    // Transparency of immage
874
0
    if (pGrd->style.iTranslucency > 0)
875
0
    {
876
0
        bOK &= VSIFPrintfL(tabfp, "  RasterStyle 8 %d\n",
877
0
                           pGrd->style.iTranslucency) > 0;
878
0
    }
879
880
0
    bOK &= VSIFPrintfL(tabfp, "begin_metadata\n") > 0;
881
0
    bOK &= VSIFPrintfL(tabfp, "\"\\MapInfo\" = \"\"\n") > 0;
882
0
    bOK &= VSIFPrintfL(tabfp, "\"\\Vm\" = \"\"\n") > 0;
883
0
    bOK &= VSIFPrintfL(tabfp, "\"\\Vm\\Grid\" = \"Numeric\"\n") > 0;
884
0
    bOK &= VSIFPrintfL(tabfp, "\"\\Vm\\GridName\" = \"%s\"\n",
885
0
                       basename.c_str()) > 0;
886
0
    bOK &= VSIFPrintfL(tabfp, "\"\\IsReadOnly\" = \"FALSE\"\n") > 0;
887
0
    bOK &= VSIFPrintfL(tabfp, "end_metadata\n") > 0;
888
889
0
    if (VSIFCloseL(tabfp) != 0)
890
0
        bOK = false;
891
892
0
    return (bOK) ? 0 : -1;
893
0
}
894
895
/************************************************************************/
896
/*                               Create()                               */
897
/************************************************************************/
898
GDALDataset *NWT_GRDDataset::Create(const char *pszFilename, int nXSize,
899
                                    int nYSize, int nBandsIn,
900
                                    GDALDataType eType,
901
                                    CSLConstList papszParamList)
902
0
{
903
0
    if (nBandsIn != 1)
904
0
    {
905
0
        CPLError(CE_Failure, CPLE_FileIO,
906
0
                 "Only single band datasets are supported for writing");
907
0
        return nullptr;
908
0
    }
909
0
    if (eType != GDT_Float32)
910
0
    {
911
0
        CPLError(CE_Failure, CPLE_FileIO,
912
0
                 "Float32 is the only supported data type");
913
0
        return nullptr;
914
0
    }
915
0
    NWT_GRDDataset *poDS = new NWT_GRDDataset();
916
0
    poDS->eAccess = GA_Update;
917
0
    poDS->pGrd = static_cast<NWT_GRID *>(calloc(1, sizeof(NWT_GRID)));
918
0
    if (!poDS->pGrd)
919
0
    {
920
0
        delete poDS;
921
0
        return nullptr;
922
0
    }
923
924
    // We currently only support GRD grid types (could potentially support GRC
925
    // in the papszParamList). Also only support GDT_Float32 as the data type.
926
    // GRD format allows for data to be stretched to 32bit or 16bit integers on
927
    // disk, so it would be feasible to support other data types
928
0
    poDS->pGrd->cFormat = 0x00;
929
930
    // File version
931
0
    poDS->pGrd->fVersion = 2.0;
932
933
    // Dimensions
934
0
    poDS->pGrd->nXSide = nXSize;
935
0
    poDS->pGrd->nYSide = nYSize;
936
0
    poDS->nRasterXSize = nXSize;
937
0
    poDS->nRasterYSize = nYSize;
938
939
    // Some default values to get started with. These will
940
    // be altered when SetGeoTransform is called.
941
0
    poDS->pGrd->dfMinX = -2E+307;
942
0
    poDS->pGrd->dfMinY = -2E+307;
943
0
    poDS->pGrd->dfMaxX = 2E+307;
944
0
    poDS->pGrd->dfMaxY = 2E+307;
945
946
0
    float fZMin, fZMax;
947
    // See if the user passed the min/max values
948
0
    if (CSLFetchNameValue(papszParamList, "ZMIN") == nullptr)
949
0
    {
950
0
        fZMin = static_cast<float>(-2E+37);
951
0
    }
952
0
    else
953
0
    {
954
0
        fZMin = static_cast<float>(
955
0
            CPLAtof(CSLFetchNameValue(papszParamList, "ZMIN")));
956
0
    }
957
958
0
    if (CSLFetchNameValue(papszParamList, "ZMAX") == nullptr)
959
0
    {
960
0
        fZMax = static_cast<float>(2E+38);
961
0
    }
962
0
    else
963
0
    {
964
0
        fZMax = static_cast<float>(
965
0
            CPLAtof(CSLFetchNameValue(papszParamList, "ZMAX")));
966
0
    }
967
968
0
    poDS->pGrd->fZMin = fZMin;
969
0
    poDS->pGrd->fZMax = fZMax;
970
    // pGrd->dfStepSize = (pGrd->dfMaxX - pGrd->dfMinX) / (pGrd->nXSide - 1);
971
0
    poDS->pGrd->fZMinScale = fZMin;
972
0
    poDS->pGrd->fZMaxScale = fZMax;
973
    // poDS->pGrd->iZUnits
974
0
    memset(poDS->pGrd->cZUnits, 0, 32);
975
0
    memset(poDS->pGrd->cMICoordSys, 0, 256);
976
977
    // Some default colour inflections; Basic scale from blue to red
978
0
    poDS->pGrd->iNumColorInflections = 3;
979
980
    // Lowest inflection
981
0
    poDS->pGrd->stInflection[0].zVal = poDS->pGrd->fZMin;
982
0
    poDS->pGrd->stInflection[0].r = 0;
983
0
    poDS->pGrd->stInflection[0].g = 0;
984
0
    poDS->pGrd->stInflection[0].b = 255;
985
986
    // Mean inflection
987
0
    poDS->pGrd->stInflection[1].zVal =
988
0
        (poDS->pGrd->fZMax - poDS->pGrd->fZMin) / 2;
989
0
    poDS->pGrd->stInflection[1].r = 255;
990
0
    poDS->pGrd->stInflection[1].g = 255;
991
0
    poDS->pGrd->stInflection[1].b = 0;
992
993
    // Highest inflection
994
0
    poDS->pGrd->stInflection[2].zVal = poDS->pGrd->fZMax;
995
0
    poDS->pGrd->stInflection[2].r = 255;
996
0
    poDS->pGrd->stInflection[2].g = 0;
997
0
    poDS->pGrd->stInflection[2].b = 0;
998
999
0
    poDS->pGrd->bHillShadeExists = FALSE;
1000
0
    poDS->pGrd->bShowGradient = FALSE;
1001
0
    poDS->pGrd->bShowHillShade = FALSE;
1002
0
    poDS->pGrd->cHillShadeBrightness = 0;
1003
0
    poDS->pGrd->cHillShadeContrast = 0;
1004
0
    poDS->pGrd->fHillShadeAzimuth = 0;
1005
0
    poDS->pGrd->fHillShadeAngle = 0;
1006
1007
    // Set the raster style settings. These aren't used anywhere other than to
1008
    // write the TAB file
1009
0
    if (CSLFetchNameValue(papszParamList, "BRIGHTNESS") == nullptr)
1010
0
    {
1011
0
        poDS->pGrd->style.iBrightness = 50;
1012
0
    }
1013
0
    else
1014
0
    {
1015
0
        poDS->pGrd->style.iBrightness =
1016
0
            atoi(CSLFetchNameValue(papszParamList, "BRIGHTNESS"));
1017
0
    }
1018
1019
0
    if (CSLFetchNameValue(papszParamList, "CONTRAST") == nullptr)
1020
0
    {
1021
0
        poDS->pGrd->style.iContrast = 50;
1022
0
    }
1023
0
    else
1024
0
    {
1025
0
        poDS->pGrd->style.iContrast =
1026
0
            atoi(CSLFetchNameValue(papszParamList, "CONTRAST"));
1027
0
    }
1028
1029
0
    if (CSLFetchNameValue(papszParamList, "TRANSCOLOR") == nullptr)
1030
0
    {
1031
0
        poDS->pGrd->style.iTransColour = 0;
1032
0
    }
1033
0
    else
1034
0
    {
1035
0
        poDS->pGrd->style.iTransColour =
1036
0
            atoi(CSLFetchNameValue(papszParamList, "TRANSCOLOR"));
1037
0
    }
1038
1039
0
    if (CSLFetchNameValue(papszParamList, "TRANSLUCENCY") == nullptr)
1040
0
    {
1041
0
        poDS->pGrd->style.iTranslucency = 0;
1042
0
    }
1043
0
    else
1044
0
    {
1045
0
        poDS->pGrd->style.iTranslucency =
1046
0
            atoi(CSLFetchNameValue(papszParamList, "TRANSLUCENCY"));
1047
0
    }
1048
1049
0
    poDS->pGrd->style.bGreyscale = FALSE;
1050
0
    poDS->pGrd->style.bGrey = FALSE;
1051
0
    poDS->pGrd->style.bColour = FALSE;
1052
0
    poDS->pGrd->style.bTransparent = FALSE;
1053
1054
    // Open the grid file
1055
0
    poDS->fp = VSIFOpenL(pszFilename, "wb");
1056
0
    if (poDS->fp == nullptr)
1057
0
    {
1058
0
        CPLError(CE_Failure, CPLE_FileIO, "Failed to create GRD file");
1059
0
        nwtCloseGrid(poDS->pGrd);
1060
0
        poDS->pGrd = nullptr;
1061
0
        delete poDS;
1062
0
        return nullptr;
1063
0
    }
1064
1065
0
    poDS->pGrd->fp = poDS->fp;
1066
0
    strncpy(poDS->pGrd->szFileName, pszFilename,
1067
0
            sizeof(poDS->pGrd->szFileName) - 1);
1068
0
    poDS->pGrd->szFileName[sizeof(poDS->pGrd->szFileName) - 1] = '\0';
1069
1070
    // Seek to the start of the file and enter the default header info
1071
0
    VSIFSeekL(poDS->fp, 0, SEEK_SET);
1072
0
    if (poDS->UpdateHeader() != 0)
1073
0
    {
1074
0
        CPLError(CE_Failure, CPLE_FileIO, "Failed to create GRD file");
1075
0
        poDS->pGrd->fp = nullptr;
1076
0
        nwtCloseGrid(poDS->pGrd);
1077
0
        poDS->pGrd = nullptr;
1078
0
        delete poDS;
1079
0
        return nullptr;
1080
0
    }
1081
1082
    /* -------------------------------------------------------------------- */
1083
    /*      Create band information objects;                                */
1084
    /*      Only 1 band is allowed                                          */
1085
    /* -------------------------------------------------------------------- */
1086
0
    poDS->SetBand(1, new NWT_GRDRasterBand(poDS, 1, 1));  // z
1087
1088
0
    poDS->oOvManager.Initialize(poDS, pszFilename);
1089
0
    poDS->FlushCache(false);  // Write the header to disk.
1090
1091
0
    return poDS;
1092
0
}
1093
1094
/************************************************************************/
1095
/*                             CreateCopy()                             */
1096
/************************************************************************/
1097
GDALDataset *NWT_GRDDataset::CreateCopy(const char *pszFilename,
1098
                                        GDALDataset *poSrcDS, int bStrict,
1099
                                        CSLConstList papszOptions,
1100
                                        GDALProgressFunc pfnProgress,
1101
                                        void *pProgressData)
1102
0
{
1103
1104
0
    if (poSrcDS->GetRasterCount() != 1)
1105
0
    {
1106
0
        CPLError(CE_Failure, CPLE_FileIO,
1107
0
                 "Only single band datasets are supported for writing");
1108
0
        return nullptr;
1109
0
    }
1110
1111
0
    char **tmpOptions = CSLDuplicate(papszOptions);
1112
1113
    /*
1114
     * Compute the statistics if ZMAX and ZMIN are not provided
1115
     */
1116
0
    double dfMin = 0.0;
1117
0
    double dfMax = 0.0;
1118
0
    double dfMean = 0.0;
1119
0
    double dfStdDev = 0.0;
1120
0
    GDALRasterBand *pBand = poSrcDS->GetRasterBand(1);
1121
0
    char sMax[10] = {};
1122
0
    char sMin[10] = {};
1123
1124
0
    if ((CSLFetchNameValue(papszOptions, "ZMAX") == nullptr) ||
1125
0
        (CSLFetchNameValue(papszOptions, "ZMIN") == nullptr))
1126
0
    {
1127
0
        CPL_IGNORE_RET_VAL(pBand->GetStatistics(FALSE, TRUE, &dfMin, &dfMax,
1128
0
                                                &dfMean, &dfStdDev));
1129
0
    }
1130
1131
0
    if (CSLFetchNameValue(papszOptions, "ZMAX") == nullptr)
1132
0
    {
1133
0
        CPLsnprintf(sMax, sizeof(sMax), "%f", dfMax);
1134
0
        tmpOptions = CSLSetNameValue(tmpOptions, "ZMAX", sMax);
1135
0
    }
1136
0
    if (CSLFetchNameValue(papszOptions, "ZMIN") == nullptr)
1137
0
    {
1138
0
        CPLsnprintf(sMin, sizeof(sMin), "%f", dfMin);
1139
0
        tmpOptions = CSLSetNameValue(tmpOptions, "ZMIN", sMin);
1140
0
    }
1141
1142
0
    GDALDriver *poDriver =
1143
0
        GDALDriver::FromHandle(GDALGetDriverByName("NWT_GRD"));
1144
0
    GDALDataset *poDstDS = poDriver->DefaultCreateCopy(
1145
0
        pszFilename, poSrcDS, bStrict, tmpOptions, pfnProgress, pProgressData);
1146
1147
0
    CSLDestroy(tmpOptions);
1148
1149
0
    return poDstDS;
1150
0
}
1151
#endif  // NO_MITAB_SUPPORT
1152
1153
/************************************************************************/
1154
/*                          GDALRegister_GRD()                          */
1155
/************************************************************************/
1156
void GDALRegister_NWT_GRD()
1157
22
{
1158
22
    if (GDALGetDriverByName("NWT_GRD") != nullptr)
1159
0
        return;
1160
1161
22
    GDALDriver *poDriver = new GDALDriver();
1162
1163
22
    poDriver->SetDescription("NWT_GRD");
1164
22
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1165
22
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
1166
22
                              "Northwood Numeric Grid Format .grd/.tab");
1167
22
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/nwtgrd.html");
1168
22
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "grd");
1169
22
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1170
1171
22
    poDriver->SetMetadataItem(
1172
22
        GDAL_DMD_OPENOPTIONLIST,
1173
22
        "<OpenOptionList>"
1174
22
        "    <Option name='BAND_COUNT' type='int' description='1 (Z) or 4 "
1175
22
        "(RGBZ). Only used in read-only mode' default='4'/>"
1176
22
        "</OpenOptionList>");
1177
1178
22
    poDriver->pfnOpen = NWT_GRDDataset::Open;
1179
22
    poDriver->pfnIdentify = NWT_GRDDataset::Identify;
1180
1181
22
#ifndef NO_MITAB_SUPPORT
1182
1183
22
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Float32");
1184
22
    poDriver->SetMetadataItem(
1185
22
        GDAL_DMD_CREATIONOPTIONLIST,
1186
22
        "<CreationOptionList>"
1187
22
        "    <Option name='ZMIN' type='float' description='Minimum cell value "
1188
22
        "of raster for defining RGB scaling' default='-2E+37'/>"
1189
22
        "    <Option name='ZMAX' type='float' description='Maximum cell value "
1190
22
        "of raster for defining RGB scaling' default='2E+38'/>"
1191
22
        "    <Option name='BRIGHTNESS' type='int' description='Brightness to "
1192
22
        "be recorded in TAB file. Only affects reading with MapInfo' "
1193
22
        "default='50'/>"
1194
22
        "    <Option name='CONTRAST' type='int' description='Contrast to be "
1195
22
        "recorded in TAB file. Only affects reading with MapInfo' "
1196
22
        "default='50'/>"
1197
22
        "    <Option name='TRANSCOLOR' type='int' description='Transparent "
1198
22
        "color to be recorded in TAB file. Only affects reading with MapInfo' "
1199
22
        "default='0'/>"
1200
22
        "    <Option name='TRANSLUCENCY' type='int' description='Level of "
1201
22
        "translucency to be recorded in TAB file. Only affects reading with "
1202
22
        "MapInfo' default='0'/>"
1203
22
        "</CreationOptionList>");
1204
1205
22
    poDriver->pfnCreate = NWT_GRDDataset::Create;
1206
22
    poDriver->pfnCreateCopy = NWT_GRDDataset::CreateCopy;
1207
22
#endif
1208
1209
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
1210
22
}