Coverage Report

Created: 2025-11-15 08:43

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
                               char **papszParamList);
93
    static GDALDataset *CreateCopy(const char *pszFilename,
94
                                   GDALDataset *poSrcDS, int bStrict,
95
                                   char **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
40
    : bHaveOffsetScale(FALSE), dfOffset(0.0), dfScale(1.0), dfNoData(0.0)
143
40
{
144
40
    poDS = poDSIn;
145
40
    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
40
    if (nBand == 4 || nBands == 1)
153
10
    {
154
10
        bHaveOffsetScale = TRUE;
155
10
        dfOffset = poDSIn->pGrd->fZMin;
156
157
10
        if (poDSIn->pGrd->cFormat == 0x00)
158
5
        {
159
5
            eDataType = GDT_Float32;
160
5
            dfScale = (poDSIn->pGrd->fZMax - poDSIn->pGrd->fZMin) / SCALE16BIT;
161
5
        }
162
5
        else
163
5
        {
164
5
            eDataType = GDT_Float32;
165
5
            dfScale = (poDSIn->pGrd->fZMax - poDSIn->pGrd->fZMin) / SCALE32BIT;
166
5
        }
167
10
    }
168
30
    else
169
30
    {
170
30
        bHaveOffsetScale = FALSE;
171
30
        dfOffset = 0;
172
30
        dfScale = 1.0;
173
30
        eDataType = GDT_Byte;
174
30
    }
175
40
    nBlockXSize = poDS->GetRasterXSize();
176
40
    nBlockYSize = 1;
177
40
}
178
179
double NWT_GRDRasterBand::GetNoDataValue(int *pbSuccess)
180
712
{
181
712
    NWT_GRDDataset *poGDS = cpl::down_cast<NWT_GRDDataset *>(poDS);
182
712
    double dRetval;
183
712
    if ((nBand == 4) || (poGDS->nBands == 1))
184
682
    {
185
682
        if (pbSuccess != nullptr)
186
682
            *pbSuccess = TRUE;
187
682
        if (dfNoData != 0.0)
188
0
        {
189
0
            dRetval = dfNoData;
190
0
        }
191
682
        else
192
682
        {
193
682
            dRetval = NODATA;
194
682
        }
195
196
682
        return dRetval;
197
682
    }
198
199
30
    if (pbSuccess != nullptr)
200
30
        *pbSuccess = FALSE;
201
202
30
    return 0;
203
712
}
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
10
{
218
10
    NWT_GRDDataset *poGDS = cpl::down_cast<NWT_GRDDataset *>(poDS);
219
    // return GCI_RGB;
220
10
    if ((nBand == 4) || (poGDS->nBands == 1))
221
10
        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
10
}
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
2.76k
{
335
2.76k
    NWT_GRDDataset *poGDS = cpl::down_cast<NWT_GRDDataset *>(poDS);
336
2.76k
    if (nBlockXSize > INT_MAX / 2)
337
0
        return CE_Failure;
338
2.76k
    const int nRecordSize = nBlockXSize * 2;
339
340
    // Seek to the data position
341
2.76k
    VSIFSeekL(poGDS->fp,
342
2.76k
              1024 + nRecordSize * static_cast<vsi_l_offset>(nBlockYOff),
343
2.76k
              SEEK_SET);
344
345
2.76k
    GByte *pabyRecord = static_cast<GByte *>(VSI_MALLOC_VERBOSE(nRecordSize));
346
2.76k
    if (pabyRecord == nullptr)
347
0
        return CE_Failure;
348
349
    // Read the data
350
2.76k
    if (static_cast<int>(VSIFReadL(pabyRecord, 1, nRecordSize, poGDS->fp)) !=
351
2.76k
        nRecordSize)
352
40
    {
353
40
        CPLFree(pabyRecord);
354
40
        return CE_Failure;
355
40
    }
356
357
2.72k
    if ((nBand == 4) || (poGDS->nBands == 1))  // Z values
358
682
    {
359
682
        int bSuccess;
360
682
        const float fNoData = static_cast<float>(GetNoDataValue(&bSuccess));
361
12.7k
        for (int i = 0; i < nBlockXSize; i++)
362
12.1k
        {
363
12.1k
            unsigned short raw1;
364
12.1k
            memcpy(&raw1, pabyRecord + 2 * i, 2);
365
12.1k
            CPL_LSBPTR16(&raw1);
366
12.1k
            if (raw1 == 0)
367
2.72k
            {
368
2.72k
                static_cast<float *>(pImage)[i] = fNoData;  // null value
369
2.72k
            }
370
9.37k
            else
371
9.37k
            {
372
9.37k
                static_cast<float *>(pImage)[i] =
373
9.37k
                    static_cast<float>(dfOffset + ((raw1 - 1) * dfScale));
374
9.37k
            }
375
12.1k
        }
376
682
    }
377
2.04k
    else if (nBand == 1)  // red values
378
682
    {
379
12.7k
        for (int i = 0; i < nBlockXSize; i++)
380
12.1k
        {
381
12.1k
            unsigned short raw1;
382
12.1k
            memcpy(&raw1, pabyRecord + 2 * i, 2);
383
12.1k
            CPL_LSBPTR16(&raw1);
384
12.1k
            static_cast<unsigned char *>(pImage)[i] =
385
12.1k
                poGDS->ColorMap[raw1 / 16].r;
386
12.1k
        }
387
682
    }
388
1.36k
    else if (nBand == 2)  // green
389
682
    {
390
12.7k
        for (int i = 0; i < nBlockXSize; i++)
391
12.1k
        {
392
12.1k
            unsigned short raw1;
393
12.1k
            memcpy(&raw1, pabyRecord + 2 * i, 2);
394
12.1k
            CPL_LSBPTR16(&raw1);
395
12.1k
            static_cast<unsigned char *>(pImage)[i] =
396
12.1k
                poGDS->ColorMap[raw1 / 16].g;
397
12.1k
        }
398
682
    }
399
682
    else if (nBand == 3)  // blue
400
682
    {
401
12.7k
        for (int i = 0; i < nBlockXSize; i++)
402
12.1k
        {
403
12.1k
            unsigned short raw1;
404
12.1k
            memcpy(&raw1, pabyRecord + 2 * i, 2);
405
12.1k
            CPL_LSBPTR16(&raw1);
406
12.1k
            static_cast<unsigned char *>(pImage)[i] =
407
12.1k
                poGDS->ColorMap[raw1 / 16].b;
408
12.1k
        }
409
682
    }
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
2.72k
    CPLFree(pabyRecord);
418
419
2.72k
    return CE_None;
420
2.72k
}
421
422
/************************************************************************/
423
/* ==================================================================== */
424
/*                             NWT_GRDDataset                           */
425
/* ==================================================================== */
426
/************************************************************************/
427
428
NWT_GRDDataset::NWT_GRDDataset()
429
10
    : fp(nullptr), pGrd(nullptr), bUpdateHeader(false)
430
10
{
431
    // poCT = NULL;
432
40.9k
    for (size_t i = 0; i < CPL_ARRAYSIZE(ColorMap); ++i)
433
40.9k
    {
434
40.9k
        ColorMap[i].r = 0;
435
40.9k
        ColorMap[i].g = 0;
436
40.9k
        ColorMap[i].b = 0;
437
40.9k
    }
438
10
}
439
440
/************************************************************************/
441
/*                            ~NWT_GRDDataset()                         */
442
/************************************************************************/
443
444
NWT_GRDDataset::~NWT_GRDDataset()
445
10
{
446
447
    // Make sure any changes to the header etc are written
448
    // if we are in update mode.
449
10
    if (eAccess == GA_Update)
450
0
    {
451
0
        NWT_GRDDataset::FlushCache(true);
452
0
    }
453
10
    if (pGrd)
454
10
    {
455
10
        pGrd->fp = nullptr;  // this prevents nwtCloseGrid from closing the fp
456
10
        nwtCloseGrid(pGrd);
457
10
    }
458
10
    if (m_poSRS)
459
7
        m_poSRS->Release();
460
461
10
    if (fp != nullptr)
462
10
        VSIFCloseL(fp);
463
10
}
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
10
{
488
10
    gt[0] = pGrd->dfMinX - (pGrd->dfStepSize * 0.5);
489
10
    gt[3] = pGrd->dfMaxY + (pGrd->dfStepSize * 0.5);
490
10
    gt[1] = pGrd->dfStepSize;
491
10
    gt[2] = 0.0;
492
493
10
    gt[4] = 0.0;
494
10
    gt[5] = -1 * pGrd->dfStepSize;
495
496
10
    return CE_None;
497
10
}
498
499
/************************************************************************/
500
/*                          SetGeoTransform()                           */
501
/************************************************************************/
502
503
CPLErr NWT_GRDDataset::SetGeoTransform(const GDALGeoTransform &gt)
504
0
{
505
0
    if (gt[2] != 0.0 || gt[4] != 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[1];
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[0] + (pGrd->dfStepSize * 0.5);
518
0
    pGrd->dfMaxY = gt[3] - (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
10
{
533
534
    // First try getting it from the PAM dataset
535
10
    const OGRSpatialReference *poSRS = GDALPamDataset::GetSpatialRef();
536
10
    if (poSRS)
537
0
        return poSRS;
538
539
10
    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
10
    OGRSpatialReference *poSpatialRef =
545
10
        MITABCoordSys2SpatialRef(pGrd->cMICoordSys);
546
10
    m_poSRS = poSpatialRef;
547
548
10
    return m_poSRS;
549
10
}
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
612k
{
580
    /* -------------------------------------------------------------------- */
581
    /*  Look for the header                                                 */
582
    /* -------------------------------------------------------------------- */
583
612k
    if (poOpenInfo->nHeaderBytes < 1024)
584
569k
        return FALSE;
585
586
42.8k
    if (poOpenInfo->pabyHeader[0] != 'H' || poOpenInfo->pabyHeader[1] != 'G' ||
587
31
        poOpenInfo->pabyHeader[2] != 'P' || poOpenInfo->pabyHeader[3] != 'C' ||
588
31
        poOpenInfo->pabyHeader[4] != '1')
589
42.8k
        return FALSE;
590
591
20
    return TRUE;
592
42.8k
}
593
594
/************************************************************************/
595
/*                                Open()                                */
596
/************************************************************************/
597
GDALDataset *NWT_GRDDataset::Open(GDALOpenInfo *poOpenInfo)
598
10
{
599
10
    if (!Identify(poOpenInfo) || poOpenInfo->fpL == nullptr)
600
0
        return nullptr;
601
602
    /* -------------------------------------------------------------------- */
603
    /*      Create a corresponding GDALDataset.                             */
604
    /* -------------------------------------------------------------------- */
605
10
    int nBandsToCreate = 0;
606
607
10
    NWT_GRDDataset *poDS = new NWT_GRDDataset();
608
609
10
    poDS->fp = poOpenInfo->fpL;
610
10
    poOpenInfo->fpL = nullptr;
611
612
10
    if (poOpenInfo->eAccess == GA_Update)
613
0
    {
614
0
        nBandsToCreate = 1;
615
0
    }
616
10
    else
617
10
    {
618
10
        nBandsToCreate = atoi(CSLFetchNameValueDef(poOpenInfo->papszOpenOptions,
619
10
                                                   "BAND_COUNT", "4"));
620
10
        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
10
    }
627
10
    poDS->eAccess = poOpenInfo->eAccess;
628
629
    /* -------------------------------------------------------------------- */
630
    /*      Read the header.                                                */
631
    /* -------------------------------------------------------------------- */
632
10
    VSIFSeekL(poDS->fp, 0, SEEK_SET);
633
10
    VSIFReadL(poDS->abyHeader, 1, 1024, poDS->fp);
634
10
    poDS->pGrd = static_cast<NWT_GRID *>(calloc(1, sizeof(NWT_GRID)));
635
10
    if (!poDS->pGrd)
636
0
    {
637
0
        delete poDS;
638
0
        return nullptr;
639
0
    }
640
641
10
    poDS->pGrd->fp = poDS->fp;
642
643
10
    if (!nwt_ParseHeader(poDS->pGrd, poDS->abyHeader) ||
644
10
        !GDALCheckDatasetDimensions(poDS->pGrd->nXSide, poDS->pGrd->nYSide))
645
0
    {
646
0
        delete poDS;
647
0
        return nullptr;
648
0
    }
649
650
10
    poDS->nRasterXSize = poDS->pGrd->nXSide;
651
10
    poDS->nRasterYSize = poDS->pGrd->nYSide;
652
653
    // create a colorTable
654
    // if( poDS->pGrd->iNumColorInflections > 0 )
655
    //   poDS->CreateColorTable();
656
10
    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
50
    for (int i = 0; i < nBandsToCreate; ++i)
666
40
    {
667
40
        poDS->SetBand(i + 1,
668
40
                      new NWT_GRDRasterBand(poDS, i + 1, nBandsToCreate));
669
40
    }
670
671
    /* -------------------------------------------------------------------- */
672
    /*      Initialize any PAM information.                                 */
673
    /* -------------------------------------------------------------------- */
674
10
    poDS->SetDescription(poOpenInfo->pszFilename);
675
10
    poDS->TryLoadXML();
676
677
    /* -------------------------------------------------------------------- */
678
    /*      Check for external overviews.                                   */
679
    /* -------------------------------------------------------------------- */
680
10
    poDS->oOvManager.Initialize(poDS, poOpenInfo->pszFilename,
681
10
                                poOpenInfo->GetSiblingFiles());
682
683
10
    return poDS;
684
10
}
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, char **papszParamList)
901
0
{
902
0
    if (nBandsIn != 1)
903
0
    {
904
0
        CPLError(CE_Failure, CPLE_FileIO,
905
0
                 "Only single band datasets are supported for writing");
906
0
        return nullptr;
907
0
    }
908
0
    if (eType != GDT_Float32)
909
0
    {
910
0
        CPLError(CE_Failure, CPLE_FileIO,
911
0
                 "Float32 is the only supported data type");
912
0
        return nullptr;
913
0
    }
914
0
    NWT_GRDDataset *poDS = new NWT_GRDDataset();
915
0
    poDS->eAccess = GA_Update;
916
0
    poDS->pGrd = static_cast<NWT_GRID *>(calloc(1, sizeof(NWT_GRID)));
917
0
    if (!poDS->pGrd)
918
0
    {
919
0
        delete poDS;
920
0
        return nullptr;
921
0
    }
922
923
    // We currently only support GRD grid types (could potentially support GRC
924
    // in the papszParamList). Also only support GDT_Float32 as the data type.
925
    // GRD format allows for data to be stretched to 32bit or 16bit integers on
926
    // disk, so it would be feasible to support other data types
927
0
    poDS->pGrd->cFormat = 0x00;
928
929
    // File version
930
0
    poDS->pGrd->fVersion = 2.0;
931
932
    // Dimensions
933
0
    poDS->pGrd->nXSide = nXSize;
934
0
    poDS->pGrd->nYSide = nYSize;
935
0
    poDS->nRasterXSize = nXSize;
936
0
    poDS->nRasterYSize = nYSize;
937
938
    // Some default values to get started with. These will
939
    // be altered when SetGeoTransform is called.
940
0
    poDS->pGrd->dfMinX = -2E+307;
941
0
    poDS->pGrd->dfMinY = -2E+307;
942
0
    poDS->pGrd->dfMaxX = 2E+307;
943
0
    poDS->pGrd->dfMaxY = 2E+307;
944
945
0
    float fZMin, fZMax;
946
    // See if the user passed the min/max values
947
0
    if (CSLFetchNameValue(papszParamList, "ZMIN") == nullptr)
948
0
    {
949
0
        fZMin = static_cast<float>(-2E+37);
950
0
    }
951
0
    else
952
0
    {
953
0
        fZMin = static_cast<float>(
954
0
            CPLAtof(CSLFetchNameValue(papszParamList, "ZMIN")));
955
0
    }
956
957
0
    if (CSLFetchNameValue(papszParamList, "ZMAX") == nullptr)
958
0
    {
959
0
        fZMax = static_cast<float>(2E+38);
960
0
    }
961
0
    else
962
0
    {
963
0
        fZMax = static_cast<float>(
964
0
            CPLAtof(CSLFetchNameValue(papszParamList, "ZMAX")));
965
0
    }
966
967
0
    poDS->pGrd->fZMin = fZMin;
968
0
    poDS->pGrd->fZMax = fZMax;
969
    // pGrd->dfStepSize = (pGrd->dfMaxX - pGrd->dfMinX) / (pGrd->nXSide - 1);
970
0
    poDS->pGrd->fZMinScale = fZMin;
971
0
    poDS->pGrd->fZMaxScale = fZMax;
972
    // poDS->pGrd->iZUnits
973
0
    memset(poDS->pGrd->cZUnits, 0, 32);
974
0
    memset(poDS->pGrd->cMICoordSys, 0, 256);
975
976
    // Some default colour inflections; Basic scale from blue to red
977
0
    poDS->pGrd->iNumColorInflections = 3;
978
979
    // Lowest inflection
980
0
    poDS->pGrd->stInflection[0].zVal = poDS->pGrd->fZMin;
981
0
    poDS->pGrd->stInflection[0].r = 0;
982
0
    poDS->pGrd->stInflection[0].g = 0;
983
0
    poDS->pGrd->stInflection[0].b = 255;
984
985
    // Mean inflection
986
0
    poDS->pGrd->stInflection[1].zVal =
987
0
        (poDS->pGrd->fZMax - poDS->pGrd->fZMin) / 2;
988
0
    poDS->pGrd->stInflection[1].r = 255;
989
0
    poDS->pGrd->stInflection[1].g = 255;
990
0
    poDS->pGrd->stInflection[1].b = 0;
991
992
    // Highest inflection
993
0
    poDS->pGrd->stInflection[2].zVal = poDS->pGrd->fZMax;
994
0
    poDS->pGrd->stInflection[2].r = 255;
995
0
    poDS->pGrd->stInflection[2].g = 0;
996
0
    poDS->pGrd->stInflection[2].b = 0;
997
998
0
    poDS->pGrd->bHillShadeExists = FALSE;
999
0
    poDS->pGrd->bShowGradient = FALSE;
1000
0
    poDS->pGrd->bShowHillShade = FALSE;
1001
0
    poDS->pGrd->cHillShadeBrightness = 0;
1002
0
    poDS->pGrd->cHillShadeContrast = 0;
1003
0
    poDS->pGrd->fHillShadeAzimuth = 0;
1004
0
    poDS->pGrd->fHillShadeAngle = 0;
1005
1006
    // Set the raster style settings. These aren't used anywhere other than to
1007
    // write the TAB file
1008
0
    if (CSLFetchNameValue(papszParamList, "BRIGHTNESS") == nullptr)
1009
0
    {
1010
0
        poDS->pGrd->style.iBrightness = 50;
1011
0
    }
1012
0
    else
1013
0
    {
1014
0
        poDS->pGrd->style.iBrightness =
1015
0
            atoi(CSLFetchNameValue(papszParamList, "BRIGHTNESS"));
1016
0
    }
1017
1018
0
    if (CSLFetchNameValue(papszParamList, "CONTRAST") == nullptr)
1019
0
    {
1020
0
        poDS->pGrd->style.iContrast = 50;
1021
0
    }
1022
0
    else
1023
0
    {
1024
0
        poDS->pGrd->style.iContrast =
1025
0
            atoi(CSLFetchNameValue(papszParamList, "CONTRAST"));
1026
0
    }
1027
1028
0
    if (CSLFetchNameValue(papszParamList, "TRANSCOLOR") == nullptr)
1029
0
    {
1030
0
        poDS->pGrd->style.iTransColour = 0;
1031
0
    }
1032
0
    else
1033
0
    {
1034
0
        poDS->pGrd->style.iTransColour =
1035
0
            atoi(CSLFetchNameValue(papszParamList, "TRANSCOLOR"));
1036
0
    }
1037
1038
0
    if (CSLFetchNameValue(papszParamList, "TRANSLUCENCY") == nullptr)
1039
0
    {
1040
0
        poDS->pGrd->style.iTranslucency = 0;
1041
0
    }
1042
0
    else
1043
0
    {
1044
0
        poDS->pGrd->style.iTranslucency =
1045
0
            atoi(CSLFetchNameValue(papszParamList, "TRANSLUCENCY"));
1046
0
    }
1047
1048
0
    poDS->pGrd->style.bGreyscale = FALSE;
1049
0
    poDS->pGrd->style.bGrey = FALSE;
1050
0
    poDS->pGrd->style.bColour = FALSE;
1051
0
    poDS->pGrd->style.bTransparent = FALSE;
1052
1053
    // Open the grid file
1054
0
    poDS->fp = VSIFOpenL(pszFilename, "wb");
1055
0
    if (poDS->fp == nullptr)
1056
0
    {
1057
0
        CPLError(CE_Failure, CPLE_FileIO, "Failed to create GRD file");
1058
0
        delete poDS;
1059
0
        return nullptr;
1060
0
    }
1061
1062
0
    poDS->pGrd->fp = poDS->fp;
1063
0
    strncpy(poDS->pGrd->szFileName, pszFilename,
1064
0
            sizeof(poDS->pGrd->szFileName) - 1);
1065
0
    poDS->pGrd->szFileName[sizeof(poDS->pGrd->szFileName) - 1] = '\0';
1066
1067
    // Seek to the start of the file and enter the default header info
1068
0
    VSIFSeekL(poDS->fp, 0, SEEK_SET);
1069
0
    if (poDS->UpdateHeader() != 0)
1070
0
    {
1071
0
        CPLError(CE_Failure, CPLE_FileIO, "Failed to create GRD file");
1072
0
        delete poDS;
1073
0
        return nullptr;
1074
0
    }
1075
1076
    /* -------------------------------------------------------------------- */
1077
    /*      Create band information objects;                                */
1078
    /*      Only 1 band is allowed                                          */
1079
    /* -------------------------------------------------------------------- */
1080
0
    poDS->SetBand(1, new NWT_GRDRasterBand(poDS, 1, 1));  // z
1081
1082
0
    poDS->oOvManager.Initialize(poDS, pszFilename);
1083
0
    poDS->FlushCache(false);  // Write the header to disk.
1084
1085
0
    return poDS;
1086
0
}
1087
1088
/************************************************************************/
1089
/*                                CreateCopy()                          */
1090
/************************************************************************/
1091
GDALDataset *NWT_GRDDataset::CreateCopy(const char *pszFilename,
1092
                                        GDALDataset *poSrcDS, int bStrict,
1093
                                        char **papszOptions,
1094
                                        GDALProgressFunc pfnProgress,
1095
                                        void *pProgressData)
1096
0
{
1097
1098
0
    if (poSrcDS->GetRasterCount() != 1)
1099
0
    {
1100
0
        CPLError(CE_Failure, CPLE_FileIO,
1101
0
                 "Only single band datasets are supported for writing");
1102
0
        return nullptr;
1103
0
    }
1104
1105
0
    char **tmpOptions = CSLDuplicate(papszOptions);
1106
1107
    /*
1108
     * Compute the statistics if ZMAX and ZMIN are not provided
1109
     */
1110
0
    double dfMin = 0.0;
1111
0
    double dfMax = 0.0;
1112
0
    double dfMean = 0.0;
1113
0
    double dfStdDev = 0.0;
1114
0
    GDALRasterBand *pBand = poSrcDS->GetRasterBand(1);
1115
0
    char sMax[10] = {};
1116
0
    char sMin[10] = {};
1117
1118
0
    if ((CSLFetchNameValue(papszOptions, "ZMAX") == nullptr) ||
1119
0
        (CSLFetchNameValue(papszOptions, "ZMIN") == nullptr))
1120
0
    {
1121
0
        CPL_IGNORE_RET_VAL(pBand->GetStatistics(FALSE, TRUE, &dfMin, &dfMax,
1122
0
                                                &dfMean, &dfStdDev));
1123
0
    }
1124
1125
0
    if (CSLFetchNameValue(papszOptions, "ZMAX") == nullptr)
1126
0
    {
1127
0
        CPLsnprintf(sMax, sizeof(sMax), "%f", dfMax);
1128
0
        tmpOptions = CSLSetNameValue(tmpOptions, "ZMAX", sMax);
1129
0
    }
1130
0
    if (CSLFetchNameValue(papszOptions, "ZMIN") == nullptr)
1131
0
    {
1132
0
        CPLsnprintf(sMin, sizeof(sMin), "%f", dfMin);
1133
0
        tmpOptions = CSLSetNameValue(tmpOptions, "ZMIN", sMin);
1134
0
    }
1135
1136
0
    GDALDriver *poDriver =
1137
0
        GDALDriver::FromHandle(GDALGetDriverByName("NWT_GRD"));
1138
0
    GDALDataset *poDstDS = poDriver->DefaultCreateCopy(
1139
0
        pszFilename, poSrcDS, bStrict, tmpOptions, pfnProgress, pProgressData);
1140
1141
0
    CSLDestroy(tmpOptions);
1142
1143
0
    return poDstDS;
1144
0
}
1145
#endif  // NO_MITAB_SUPPORT
1146
1147
/************************************************************************/
1148
/*                          GDALRegister_GRD()                          */
1149
/************************************************************************/
1150
void GDALRegister_NWT_GRD()
1151
22
{
1152
22
    if (GDALGetDriverByName("NWT_GRD") != nullptr)
1153
0
        return;
1154
1155
22
    GDALDriver *poDriver = new GDALDriver();
1156
1157
22
    poDriver->SetDescription("NWT_GRD");
1158
22
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1159
22
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME,
1160
22
                              "Northwood Numeric Grid Format .grd/.tab");
1161
22
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/nwtgrd.html");
1162
22
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "grd");
1163
22
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1164
1165
22
    poDriver->SetMetadataItem(
1166
22
        GDAL_DMD_OPENOPTIONLIST,
1167
22
        "<OpenOptionList>"
1168
22
        "    <Option name='BAND_COUNT' type='int' description='1 (Z) or 4 "
1169
22
        "(RGBZ). Only used in read-only mode' default='4'/>"
1170
22
        "</OpenOptionList>");
1171
1172
22
    poDriver->pfnOpen = NWT_GRDDataset::Open;
1173
22
    poDriver->pfnIdentify = NWT_GRDDataset::Identify;
1174
1175
22
#ifndef NO_MITAB_SUPPORT
1176
1177
22
    poDriver->SetMetadataItem(GDAL_DMD_CREATIONDATATYPES, "Float32");
1178
22
    poDriver->SetMetadataItem(
1179
22
        GDAL_DMD_CREATIONOPTIONLIST,
1180
22
        "<CreationOptionList>"
1181
22
        "    <Option name='ZMIN' type='float' description='Minimum cell value "
1182
22
        "of raster for defining RGB scaling' default='-2E+37'/>"
1183
22
        "    <Option name='ZMAX' type='float' description='Maximum cell value "
1184
22
        "of raster for defining RGB scaling' default='2E+38'/>"
1185
22
        "    <Option name='BRIGHTNESS' type='int' description='Brightness to "
1186
22
        "be recorded in TAB file. Only affects reading with MapInfo' "
1187
22
        "default='50'/>"
1188
22
        "    <Option name='CONTRAST' type='int' description='Contrast to be "
1189
22
        "recorded in TAB file. Only affects reading with MapInfo' "
1190
22
        "default='50'/>"
1191
22
        "    <Option name='TRANSCOLOR' type='int' description='Transparent "
1192
22
        "color to be recorded in TAB file. Only affects reading with MapInfo' "
1193
22
        "default='0'/>"
1194
22
        "    <Option name='TRANSLUCENCY' type='int' description='Level of "
1195
22
        "translucency to be recorded in TAB file. Only affects reading with "
1196
22
        "MapInfo' default='0'/>"
1197
22
        "</CreationOptionList>");
1198
1199
22
    poDriver->pfnCreate = NWT_GRDDataset::Create;
1200
22
    poDriver->pfnCreateCopy = NWT_GRDDataset::CreateCopy;
1201
22
#endif
1202
1203
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
1204
22
}