Coverage Report

Created: 2026-09-26 08:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/ogr/ogrsf_frmts/ntf/ntf_raster.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  NTF Translator
4
 * Purpose:  Handle UK Ordnance Survey Raster DTM products.  Includes some
5
 *           raster related methods from NTFFileReader and the implementation
6
 *           of OGRNTFRasterLayer.
7
 * Author:   Frank Warmerdam, warmerdam@pobox.com
8
 *
9
 ******************************************************************************
10
 * Copyright (c) 1999, Frank Warmerdam
11
 *
12
 * SPDX-License-Identifier: MIT
13
 ****************************************************************************/
14
15
#include "ntf.h"
16
17
#include <algorithm>
18
19
/************************************************************************/
20
/* ==================================================================== */
21
/*                     NTFFileReader Raster Methods                     */
22
/* ==================================================================== */
23
/************************************************************************/
24
25
/************************************************************************/
26
/*                          IsRasterProduct()                           */
27
/************************************************************************/
28
29
int NTFFileReader::IsRasterProduct()
30
31
0
{
32
0
    return GetProductId() == NPC_LANDRANGER_DTM ||
33
0
           GetProductId() == NPC_LANDFORM_PROFILE_DTM;
34
0
}
35
36
/************************************************************************/
37
/*                       EstablishRasterAccess()                        */
38
/************************************************************************/
39
40
void NTFFileReader::EstablishRasterAccess()
41
42
0
{
43
    /* -------------------------------------------------------------------- */
44
    /*      Read the type 50 record.                                        */
45
    /* -------------------------------------------------------------------- */
46
0
    NTFRecord *poRecord = nullptr;
47
48
0
    while ((poRecord = ReadRecord()) != nullptr &&
49
0
           poRecord->GetType() != NRT_GRIDHREC &&
50
0
           poRecord->GetType() != NRT_VTR)
51
0
    {
52
0
        delete poRecord;
53
0
    }
54
55
0
    if (poRecord == nullptr || poRecord->GetType() != NRT_GRIDHREC)
56
0
    {
57
0
        delete poRecord;
58
0
        CPLError(CE_Failure, CPLE_AppDefined,
59
0
                 "Unable to find GRIDHREC (type 50) record in what appears\n"
60
0
                 "to be an NTF Raster DTM product.");
61
0
        return;
62
0
    }
63
64
    /* -------------------------------------------------------------------- */
65
    /*      Parse if LANDRANGER_DTM                                         */
66
    /* -------------------------------------------------------------------- */
67
0
    if (GetProductId() == NPC_LANDRANGER_DTM)
68
0
    {
69
0
        nRasterXSize = atoi(poRecord->GetField(13, 16));
70
0
        nRasterYSize = atoi(poRecord->GetField(17, 20));
71
72
        // NOTE: unusual use of GeoTransform - the pixel origin is the
73
        // bottom left corner!
74
0
        adfGeoTransform[0] = atoi(poRecord->GetField(25, 34));
75
0
        adfGeoTransform[1] = 50;
76
0
        adfGeoTransform[2] = 0;
77
0
        adfGeoTransform[3] = atoi(poRecord->GetField(35, 44));
78
0
        adfGeoTransform[4] = 0;
79
0
        adfGeoTransform[5] = 50;
80
81
0
        nRasterDataType = 3; /* GDT_Int16 */
82
0
    }
83
84
    /* -------------------------------------------------------------------- */
85
    /*      Parse if LANDFORM_PROFILE_DTM                                   */
86
    /* -------------------------------------------------------------------- */
87
0
    else if (GetProductId() == NPC_LANDFORM_PROFILE_DTM)
88
0
    {
89
0
        nRasterXSize = atoi(poRecord->GetField(23, 30));
90
0
        nRasterYSize = atoi(poRecord->GetField(31, 38));
91
92
        // NOTE: unusual use of GeoTransform - the pixel origin is the
93
        // bottom left corner!
94
0
        adfGeoTransform[0] = atoi(poRecord->GetField(13, 17)) + GetXOrigin();
95
0
        adfGeoTransform[1] = atoi(poRecord->GetField(39, 42));
96
0
        adfGeoTransform[2] = 0;
97
0
        adfGeoTransform[3] = atoi(poRecord->GetField(18, 22)) + GetYOrigin();
98
0
        adfGeoTransform[4] = 0;
99
0
        adfGeoTransform[5] = atoi(poRecord->GetField(43, 46));
100
101
0
        nRasterDataType = 3; /* GDT_Int16 */
102
0
    }
103
104
    /* -------------------------------------------------------------------- */
105
    /*      Initialize column offsets table.                                */
106
    /* -------------------------------------------------------------------- */
107
0
    delete poRecord;
108
109
0
    if (!GDALCheckDatasetDimensions(nRasterXSize, nRasterYSize))
110
0
        return;
111
112
0
    panColumnOffset = static_cast<vsi_l_offset *>(
113
0
        CPLCalloc(sizeof(vsi_l_offset), nRasterXSize));
114
115
0
    GetFPPos(panColumnOffset + 0, nullptr);
116
117
    /* -------------------------------------------------------------------- */
118
    /*      Create an OGRSFLayer for this file readers raster points.       */
119
    /* -------------------------------------------------------------------- */
120
0
    if (poDS != nullptr)
121
0
    {
122
0
        poRasterLayer = new OGRNTFRasterLayer(poDS, this);
123
0
        poDS->AddLayer(poRasterLayer);
124
0
    }
125
0
}
126
127
/************************************************************************/
128
/*                          ReadRasterColumn()                          */
129
/************************************************************************/
130
131
CPLErr NTFFileReader::ReadRasterColumn(int iColumn, float *pafElev)
132
133
0
{
134
    /* -------------------------------------------------------------------- */
135
    /*      If we don't already have the scanline offset of the previous    */
136
    /*      line, force reading of previous records to establish it.        */
137
    /* -------------------------------------------------------------------- */
138
0
    if (panColumnOffset[iColumn] == 0)
139
0
    {
140
0
        for (int iPrev = 0; iPrev < iColumn - 1; iPrev++)
141
0
        {
142
0
            if (panColumnOffset[iPrev + 1] == 0)
143
0
            {
144
0
                CPLErr eErr;
145
146
0
                eErr = ReadRasterColumn(iPrev, nullptr);
147
0
                if (eErr != CE_None)
148
0
                    return eErr;
149
0
            }
150
0
        }
151
0
    }
152
153
    /* -------------------------------------------------------------------- */
154
    /*      If the dataset isn't open, open it now.                         */
155
    /* -------------------------------------------------------------------- */
156
0
    if (GetFP() == nullptr)
157
0
        Open();
158
159
    /* -------------------------------------------------------------------- */
160
    /*      Read requested record.                                          */
161
    /* -------------------------------------------------------------------- */
162
0
    SetFPPos(panColumnOffset[iColumn], iColumn);
163
0
    NTFRecord *poRecord = ReadRecord();
164
0
    if (poRecord == nullptr)
165
0
        return CE_Failure;
166
167
0
    CPLErr eErr = CE_None;
168
0
    if (iColumn < nRasterXSize - 1)
169
0
    {
170
0
        GetFPPos(panColumnOffset + iColumn + 1, nullptr);
171
0
    }
172
173
    /* -------------------------------------------------------------------- */
174
    /*      Handle LANDRANGER DTM columns.                                  */
175
    /* -------------------------------------------------------------------- */
176
0
    if (pafElev != nullptr && GetProductId() == NPC_LANDRANGER_DTM)
177
0
    {
178
0
        const double dfVOffset = atoi(poRecord->GetField(56, 65));
179
0
        const double dfVScale = atoi(poRecord->GetField(66, 75)) * 0.001;
180
181
0
        for (int iPixel = 0; iPixel < nRasterYSize; iPixel++)
182
0
        {
183
0
            const char *pszValue =
184
0
                poRecord->GetField(84 + iPixel * 4, 87 + iPixel * 4);
185
0
            if (pszValue[0] == '\0' || pszValue[0] == ' ')
186
0
            {
187
0
                eErr = CE_Failure;
188
0
                break;
189
0
            }
190
0
            pafElev[iPixel] = (float)(dfVOffset + dfVScale * atoi(pszValue));
191
0
        }
192
0
    }
193
194
    /* -------------------------------------------------------------------- */
195
    /*      Handle PROFILE                                                  */
196
    /* -------------------------------------------------------------------- */
197
0
    else if (pafElev != nullptr && GetProductId() == NPC_LANDFORM_PROFILE_DTM)
198
0
    {
199
0
        for (int iPixel = 0; iPixel < nRasterYSize; iPixel++)
200
0
        {
201
0
            const char *pszValue =
202
0
                poRecord->GetField(19 + iPixel * 5, 23 + iPixel * 5);
203
0
            if (pszValue[0] == '\0' || pszValue[0] == ' ')
204
0
            {
205
0
                eErr = CE_Failure;
206
0
                break;
207
0
            }
208
0
            pafElev[iPixel] = (float)(atoi(pszValue) * GetZMult());
209
0
        }
210
0
    }
211
212
0
    delete poRecord;
213
214
0
    return eErr;
215
0
}
216
217
/************************************************************************/
218
/* ==================================================================== */
219
/*                        OGRNTFRasterLayer                             */
220
/* ==================================================================== */
221
/************************************************************************/
222
223
/************************************************************************/
224
/*                          OGRNTFRasterLayer                           */
225
/************************************************************************/
226
227
OGRNTFRasterLayer::OGRNTFRasterLayer(OGRNTFDataSource *poDSIn,
228
                                     NTFFileReader *poReaderIn)
229
0
    : poFeatureDefn(nullptr), poFilterGeom(nullptr), poReader(poReaderIn),
230
0
      pafColumn(static_cast<float *>(
231
0
          CPLCalloc(sizeof(float), poReaderIn->GetRasterYSize()))),
232
0
      iColumnOffset(-1), iCurrentFC(1),
233
      // Check for DEM subsampling.
234
0
      nDEMSample(poDSIn->GetOption("DEM_SAMPLE") == nullptr
235
0
                     ? 1
236
0
                     : std::max(1, atoi(poDSIn->GetOption("DEM_SAMPLE")))),
237
0
      nFeatureCount(0)
238
0
{
239
0
    char szLayerName[128];
240
0
    snprintf(szLayerName, sizeof(szLayerName), "DTM_%s",
241
0
             poReaderIn->GetTileName());
242
0
    poFeatureDefn = new OGRFeatureDefn(szLayerName);
243
244
0
    poFeatureDefn->Reference();
245
0
    poFeatureDefn->SetGeomType(wkbPoint25D);
246
0
    poFeatureDefn->GetGeomFieldDefn(0)->SetSpatialRef(
247
0
        poDSIn->DSGetSpatialRef());
248
249
0
    OGRFieldDefn oHeight("HEIGHT", OFTReal);
250
0
    poFeatureDefn->AddFieldDefn(&oHeight);
251
252
0
    nFeatureCount =
253
0
        static_cast<GIntBig>(poReader->GetRasterXSize() / nDEMSample) *
254
0
        (poReader->GetRasterYSize() / nDEMSample);
255
0
}
256
257
/************************************************************************/
258
/*                         ~OGRNTFRasterLayer()                         */
259
/************************************************************************/
260
261
OGRNTFRasterLayer::~OGRNTFRasterLayer()
262
263
0
{
264
0
    CPLFree(pafColumn);
265
0
    if (poFeatureDefn)
266
0
        poFeatureDefn->Release();
267
268
0
    if (poFilterGeom != nullptr)
269
0
        delete poFilterGeom;
270
0
}
271
272
/************************************************************************/
273
/*                            ResetReading()                            */
274
/************************************************************************/
275
276
void OGRNTFRasterLayer::ResetReading()
277
278
0
{
279
0
    iCurrentFC = 1;
280
0
}
281
282
/************************************************************************/
283
/*                           GetNextFeature()                           */
284
/************************************************************************/
285
286
OGRFeature *OGRNTFRasterLayer::GetNextFeature()
287
288
0
{
289
0
    if (iCurrentFC > static_cast<GIntBig>(poReader->GetRasterXSize()) *
290
0
                         poReader->GetRasterYSize())
291
0
    {
292
0
        return nullptr;
293
0
    }
294
295
0
    OGRFeature *poFeature = GetFeature(iCurrentFC);
296
297
0
    int iReqColumn, iReqRow;
298
299
0
    iReqColumn =
300
0
        static_cast<int>((iCurrentFC - 1) / poReader->GetRasterYSize());
301
0
    iReqRow = static_cast<int>(
302
0
        iCurrentFC -
303
0
        static_cast<GIntBig>(iReqColumn) * poReader->GetRasterYSize() - 1);
304
305
0
    if (iReqRow + nDEMSample > poReader->GetRasterYSize())
306
0
    {
307
0
        iReqRow = 0;
308
0
        iReqColumn += nDEMSample;
309
0
    }
310
0
    else
311
0
    {
312
0
        iReqRow += nDEMSample;
313
0
    }
314
315
0
    iCurrentFC = static_cast<GIntBig>(iReqColumn) * poReader->GetRasterYSize() +
316
0
                 iReqRow + 1;
317
318
0
    return poFeature;
319
0
}
320
321
/************************************************************************/
322
/*                             GetFeature()                             */
323
/************************************************************************/
324
325
OGRFeature *OGRNTFRasterLayer::GetFeature(GIntBig nFeatureId)
326
327
0
{
328
0
    int iReqColumn, iReqRow;
329
330
    /* -------------------------------------------------------------------- */
331
    /*      Is this in the range of legal feature ids (pixels)?             */
332
    /* -------------------------------------------------------------------- */
333
0
    if (nFeatureId < 1 ||
334
0
        nFeatureId > static_cast<GIntBig>(poReader->GetRasterXSize()) *
335
0
                         poReader->GetRasterYSize())
336
0
    {
337
0
        return nullptr;
338
0
    }
339
340
    /* -------------------------------------------------------------------- */
341
    /*      Do we need to load a different column.                          */
342
    /* -------------------------------------------------------------------- */
343
0
    iReqColumn =
344
0
        static_cast<int>((nFeatureId - 1) / poReader->GetRasterYSize());
345
0
    iReqRow = static_cast<int>(
346
0
        nFeatureId -
347
0
        static_cast<GIntBig>(iReqColumn) * poReader->GetRasterYSize() - 1);
348
349
0
    if (iReqColumn != iColumnOffset)
350
0
    {
351
0
        iColumnOffset = iReqColumn;
352
0
        if (poReader->ReadRasterColumn(iReqColumn, pafColumn) != CE_None)
353
0
            return nullptr;
354
0
    }
355
0
    if (iReqRow < 0 || iReqRow >= poReader->GetRasterYSize())
356
0
        return nullptr;
357
358
    /* -------------------------------------------------------------------- */
359
    /*      Create a corresponding feature.                                 */
360
    /* -------------------------------------------------------------------- */
361
0
    OGRFeature *poFeature = new OGRFeature(poFeatureDefn);
362
0
    double *padfGeoTransform = poReader->GetGeoTransform();
363
364
0
    poFeature->SetFID(nFeatureId);
365
366
    // NOTE: unusual use of GeoTransform - the pixel origin is the
367
    // bottom left corner!
368
0
    poFeature->SetGeometryDirectly(
369
0
        new OGRPoint(padfGeoTransform[0] + padfGeoTransform[1] * iReqColumn,
370
0
                     padfGeoTransform[3] + padfGeoTransform[5] * iReqRow,
371
0
                     pafColumn[iReqRow]));
372
0
    poFeature->SetField(0, pafColumn[iReqRow]);
373
374
0
    return poFeature;
375
0
}
376
377
/************************************************************************/
378
/*                          GetFeatureCount()                           */
379
/*                                                                      */
380
/*      If a spatial filter is in effect, we turn control over to       */
381
/*      the generic counter.  Otherwise we return the total count.      */
382
/*      Eventually we should consider implementing a more efficient     */
383
/*      way of counting features matching a spatial query.              */
384
/************************************************************************/
385
386
GIntBig OGRNTFRasterLayer::GetFeatureCount(CPL_UNUSED int bForce)
387
0
{
388
0
    return nFeatureCount;
389
0
}
390
391
/************************************************************************/
392
/*                           TestCapability()                           */
393
/************************************************************************/
394
395
bool OGRNTFRasterLayer::TestCapability(const char *pszCap) const
396
397
0
{
398
0
    if (EQUAL(pszCap, OLCRandomRead))
399
0
        return TRUE;
400
401
0
    else if (EQUAL(pszCap, OLCFastFeatureCount))
402
0
        return TRUE;
403
404
0
    return FALSE;
405
0
}