Coverage Report

Created: 2026-06-30 08:33

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/gdal/frmts/raw/mffdataset.cpp
Line
Count
Source
1
/******************************************************************************
2
 *
3
 * Project:  GView
4
 * Purpose:  Implementation of Atlantis MFF Support
5
 * Author:   Frank Warmerdam, warmerda@home.com
6
 *
7
 ******************************************************************************
8
 * Copyright (c) 2000, Frank Warmerdam
9
 * Copyright (c) 2008-2012, Even Rouault <even dot rouault at spatialys.com>
10
 *
11
 * SPDX-License-Identifier: MIT
12
 ****************************************************************************/
13
14
#include "atlsci_spheroid.h"
15
#include "cpl_string.h"
16
#include "gdal_frmts.h"
17
#include "gdal_priv.h"
18
#include "ogr_spatialref.h"
19
#include "rawdataset.h"
20
21
#include <cctype>
22
#include <cmath>
23
#include <algorithm>
24
25
enum
26
{
27
    MFFPRJ_NONE,
28
    MFFPRJ_LL,
29
    MFFPRJ_UTM,
30
    MFFPRJ_UNRECOGNIZED
31
};
32
33
/************************************************************************/
34
/* ==================================================================== */
35
/*                              MFFDataset                              */
36
/* ==================================================================== */
37
/************************************************************************/
38
39
class MFFDataset final : public RawDataset
40
{
41
    int nGCPCount;
42
    GDAL_GCP *pasGCPList;
43
44
    OGRSpatialReference m_oSRS{};
45
    OGRSpatialReference m_oGCPSRS{};
46
    GDALGeoTransform m_gt{};
47
    char **m_papszFileList;
48
49
    void ScanForGCPs();
50
    void ScanForProjectionInfo();
51
52
    CPL_DISALLOW_COPY_ASSIGN(MFFDataset)
53
54
    CPLErr Close(GDALProgressFunc = nullptr, void * = nullptr) override;
55
56
  public:
57
    MFFDataset();
58
    ~MFFDataset() override;
59
60
    char **papszHdrLines;
61
62
    VSILFILE **pafpBandFiles;
63
64
    char **GetFileList() override;
65
66
    int GetGCPCount() override;
67
68
    const OGRSpatialReference *GetGCPSpatialRef() const override
69
0
    {
70
0
        return m_oGCPSRS.IsEmpty() ? nullptr : &m_oGCPSRS;
71
0
    }
72
73
    const GDAL_GCP *GetGCPs() override;
74
75
    const OGRSpatialReference *GetSpatialRef() const override
76
0
    {
77
0
        return m_oSRS.IsEmpty() ? nullptr : &m_oSRS;
78
0
    }
79
80
    CPLErr GetGeoTransform(GDALGeoTransform &gt) const override;
81
82
    static GDALDataset *Open(GDALOpenInfo *);
83
};
84
85
/************************************************************************/
86
/* ==================================================================== */
87
/*                            MFFTiledBand                              */
88
/* ==================================================================== */
89
/************************************************************************/
90
91
class MFFTiledBand final : public GDALRasterBand
92
{
93
    friend class MFFDataset;
94
95
    VSILFILE *fpRaw;
96
    RawRasterBand::ByteOrder eByteOrder;
97
98
    CPL_DISALLOW_COPY_ASSIGN(MFFTiledBand)
99
100
  public:
101
    MFFTiledBand(MFFDataset *, int, VSILFILE *, int, int, GDALDataType,
102
                 RawRasterBand::ByteOrder);
103
    ~MFFTiledBand() override;
104
105
    CPLErr IReadBlock(int, int, void *) override;
106
};
107
108
/************************************************************************/
109
/*                            MFFTiledBand()                            */
110
/************************************************************************/
111
112
MFFTiledBand::MFFTiledBand(MFFDataset *poDSIn, int nBandIn, VSILFILE *fp,
113
                           int nTileXSize, int nTileYSize,
114
                           GDALDataType eDataTypeIn,
115
                           RawRasterBand::ByteOrder eByteOrderIn)
116
0
    : fpRaw(fp), eByteOrder(eByteOrderIn)
117
0
{
118
0
    poDS = poDSIn;
119
0
    nBand = nBandIn;
120
121
0
    eDataType = eDataTypeIn;
122
123
0
    nBlockXSize = nTileXSize;
124
0
    nBlockYSize = nTileYSize;
125
0
}
126
127
/************************************************************************/
128
/*                           ~MFFTiledBand()                            */
129
/************************************************************************/
130
131
MFFTiledBand::~MFFTiledBand()
132
133
0
{
134
0
    if (VSIFCloseL(fpRaw) != 0)
135
0
    {
136
0
        CPLError(CE_Failure, CPLE_FileIO, "I/O error");
137
0
    }
138
0
}
139
140
/************************************************************************/
141
/*                             IReadBlock()                             */
142
/************************************************************************/
143
144
CPLErr MFFTiledBand::IReadBlock(int nBlockXOff, int nBlockYOff, void *pImage)
145
146
0
{
147
0
    const int nTilesPerRow = DIV_ROUND_UP(nRasterXSize, nBlockXSize);
148
0
    const int nWordSize = GDALGetDataTypeSizeBytes(eDataType);
149
0
    const int nBlockSize = nWordSize * nBlockXSize * nBlockYSize;
150
151
0
    const vsi_l_offset nOffset =
152
0
        nBlockSize *
153
0
        (nBlockXOff + static_cast<vsi_l_offset>(nBlockYOff) * nTilesPerRow);
154
155
0
    if (VSIFSeekL(fpRaw, nOffset, SEEK_SET) == -1 ||
156
0
        VSIFReadL(pImage, 1, nBlockSize, fpRaw) < 1)
157
0
    {
158
0
        CPLError(CE_Failure, CPLE_FileIO,
159
0
                 "Read of tile %d/%d failed with fseek or fread error.",
160
0
                 nBlockXOff, nBlockYOff);
161
0
        return CE_Failure;
162
0
    }
163
164
0
    if (eByteOrder != RawRasterBand::NATIVE_BYTE_ORDER && nWordSize > 1)
165
0
    {
166
0
        if (GDALDataTypeIsComplex(eDataType))
167
0
        {
168
0
            GDALSwapWords(pImage, nWordSize / 2, nBlockXSize * nBlockYSize,
169
0
                          nWordSize);
170
0
            GDALSwapWords(reinterpret_cast<GByte *>(pImage) + nWordSize / 2,
171
0
                          nWordSize / 2, nBlockXSize * nBlockYSize, nWordSize);
172
0
        }
173
0
        else
174
0
            GDALSwapWords(pImage, nWordSize, nBlockXSize * nBlockYSize,
175
0
                          nWordSize);
176
0
    }
177
178
0
    return CE_None;
179
0
}
180
181
/************************************************************************/
182
/*                            MFF Spheroids                             */
183
/************************************************************************/
184
185
class MFFSpheroidList final : public SpheroidList
186
{
187
  public:
188
    MFFSpheroidList();
189
};
190
191
MFFSpheroidList ::MFFSpheroidList()
192
0
{
193
0
    num_spheroids = 18;
194
195
0
    epsilonR = 0.1;
196
0
    epsilonI = 0.000001;
197
198
0
    spheroids[0].SetValuesByRadii("SPHERE", 6371007.0, 6371007.0);
199
0
    spheroids[1].SetValuesByRadii("EVEREST", 6377304.0, 6356103.0);
200
0
    spheroids[2].SetValuesByRadii("BESSEL", 6377397.0, 6356082.0);
201
0
    spheroids[3].SetValuesByRadii("AIRY", 6377563.0, 6356300.0);
202
0
    spheroids[4].SetValuesByRadii("CLARKE_1858", 6378294.0, 6356621.0);
203
0
    spheroids[5].SetValuesByRadii("CLARKE_1866", 6378206.4, 6356583.8);
204
0
    spheroids[6].SetValuesByRadii("CLARKE_1880", 6378249.0, 6356517.0);
205
0
    spheroids[7].SetValuesByRadii("HAYFORD", 6378388.0, 6356915.0);
206
0
    spheroids[8].SetValuesByRadii("KRASOVSKI", 6378245.0, 6356863.0);
207
0
    spheroids[9].SetValuesByRadii("HOUGH", 6378270.0, 6356794.0);
208
0
    spheroids[10].SetValuesByRadii("FISHER_60", 6378166.0, 6356784.0);
209
0
    spheroids[11].SetValuesByRadii("KAULA", 6378165.0, 6356345.0);
210
0
    spheroids[12].SetValuesByRadii("IUGG_67", 6378160.0, 6356775.0);
211
0
    spheroids[13].SetValuesByRadii("FISHER_68", 6378150.0, 6356330.0);
212
0
    spheroids[14].SetValuesByRadii("WGS_72", 6378135.0, 6356751.0);
213
0
    spheroids[15].SetValuesByRadii("IUGG_75", 6378140.0, 6356755.0);
214
0
    spheroids[16].SetValuesByRadii("WGS_84", 6378137.0, 6356752.0);
215
0
    spheroids[17].SetValuesByRadii("HUGHES", 6378273.0, 6356889.4);
216
0
}
217
218
/************************************************************************/
219
/* ==================================================================== */
220
/*                              MFFDataset                              */
221
/* ==================================================================== */
222
/************************************************************************/
223
224
/************************************************************************/
225
/*                             MFFDataset()                             */
226
/************************************************************************/
227
228
MFFDataset::MFFDataset()
229
0
    : nGCPCount(0), pasGCPList(nullptr), m_papszFileList(nullptr),
230
0
      papszHdrLines(nullptr), pafpBandFiles(nullptr)
231
0
{
232
0
    m_oSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
233
0
    m_oGCPSRS.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
234
0
}
235
236
/************************************************************************/
237
/*                            ~MFFDataset()                             */
238
/************************************************************************/
239
240
MFFDataset::~MFFDataset()
241
242
0
{
243
0
    MFFDataset::Close();
244
0
}
245
246
/************************************************************************/
247
/*                               Close()                                */
248
/************************************************************************/
249
250
CPLErr MFFDataset::Close(GDALProgressFunc, void *)
251
0
{
252
0
    CPLErr eErr = CE_None;
253
0
    if (nOpenFlags != OPEN_FLAGS_CLOSED)
254
0
    {
255
0
        if (MFFDataset::FlushCache(true) != CE_None)
256
0
            eErr = CE_Failure;
257
258
0
        CSLDestroy(papszHdrLines);
259
0
        if (pafpBandFiles)
260
0
        {
261
0
            for (int i = 0; i < GetRasterCount(); i++)
262
0
            {
263
0
                if (pafpBandFiles[i])
264
0
                {
265
0
                    if (VSIFCloseL(pafpBandFiles[i]) != 0)
266
0
                    {
267
0
                        eErr = CE_Failure;
268
0
                        CPLError(CE_Failure, CPLE_FileIO, "I/O error");
269
0
                    }
270
0
                }
271
0
            }
272
0
            CPLFree(pafpBandFiles);
273
0
        }
274
275
0
        if (nGCPCount > 0)
276
0
        {
277
0
            GDALDeinitGCPs(nGCPCount, pasGCPList);
278
0
        }
279
0
        CPLFree(pasGCPList);
280
0
        CSLDestroy(m_papszFileList);
281
282
0
        if (GDALPamDataset::Close() != CE_None)
283
0
            eErr = CE_Failure;
284
0
    }
285
0
    return eErr;
286
0
}
287
288
/************************************************************************/
289
/*                            GetFileList()                             */
290
/************************************************************************/
291
292
char **MFFDataset::GetFileList()
293
0
{
294
0
    char **papszFileList = RawDataset::GetFileList();
295
0
    papszFileList = CSLInsertStrings(papszFileList, -1, m_papszFileList);
296
0
    return papszFileList;
297
0
}
298
299
/************************************************************************/
300
/*                            GetGCPCount()                             */
301
/************************************************************************/
302
303
int MFFDataset::GetGCPCount()
304
305
0
{
306
0
    return nGCPCount;
307
0
}
308
309
/************************************************************************/
310
/*                          GetGeoTransform()                           */
311
/************************************************************************/
312
313
CPLErr MFFDataset::GetGeoTransform(GDALGeoTransform &gt) const
314
0
{
315
0
    gt = m_gt;
316
0
    return CE_None;
317
0
}
318
319
/************************************************************************/
320
/*                               GetGCP()                               */
321
/************************************************************************/
322
323
const GDAL_GCP *MFFDataset::GetGCPs()
324
325
0
{
326
0
    return pasGCPList;
327
0
}
328
329
/************************************************************************/
330
/*                            ScanForGCPs()                             */
331
/************************************************************************/
332
333
void MFFDataset::ScanForGCPs()
334
335
0
{
336
0
    int NUM_GCPS = 0;
337
338
0
    if (CSLFetchNameValue(papszHdrLines, "NUM_GCPS") != nullptr)
339
0
        NUM_GCPS = atoi(CSLFetchNameValue(papszHdrLines, "NUM_GCPS"));
340
0
    if (NUM_GCPS < 0)
341
0
        return;
342
343
0
    nGCPCount = 0;
344
0
    pasGCPList =
345
0
        static_cast<GDAL_GCP *>(VSICalloc(sizeof(GDAL_GCP), 5 + NUM_GCPS));
346
0
    if (pasGCPList == nullptr)
347
0
        return;
348
349
0
    for (int nCorner = 0; nCorner < 5; nCorner++)
350
0
    {
351
0
        const char *pszBase = nullptr;
352
0
        double dfRasterX = 0.0;
353
0
        double dfRasterY = 0.0;
354
355
0
        if (nCorner == 0)
356
0
        {
357
0
            dfRasterX = 0.5;
358
0
            dfRasterY = 0.5;
359
0
            pszBase = "TOP_LEFT_CORNER";
360
0
        }
361
0
        else if (nCorner == 1)
362
0
        {
363
0
            dfRasterX = GetRasterXSize() - 0.5;
364
0
            dfRasterY = 0.5;
365
0
            pszBase = "TOP_RIGHT_CORNER";
366
0
        }
367
0
        else if (nCorner == 2)
368
0
        {
369
0
            dfRasterX = GetRasterXSize() - 0.5;
370
0
            dfRasterY = GetRasterYSize() - 0.5;
371
0
            pszBase = "BOTTOM_RIGHT_CORNER";
372
0
        }
373
0
        else if (nCorner == 3)
374
0
        {
375
0
            dfRasterX = 0.5;
376
0
            dfRasterY = GetRasterYSize() - 0.5;
377
0
            pszBase = "BOTTOM_LEFT_CORNER";
378
0
        }
379
0
        else /* if( nCorner == 4 ) */
380
0
        {
381
0
            dfRasterX = GetRasterXSize() / 2.0;
382
0
            dfRasterY = GetRasterYSize() / 2.0;
383
0
            pszBase = "CENTRE";
384
0
        }
385
386
0
        char szLatName[40] = {'\0'};
387
0
        char szLongName[40] = {'\0'};
388
0
        snprintf(szLatName, sizeof(szLatName), "%s_LATITUDE", pszBase);
389
0
        snprintf(szLongName, sizeof(szLongName), "%s_LONGITUDE", pszBase);
390
391
0
        if (CSLFetchNameValue(papszHdrLines, szLatName) != nullptr &&
392
0
            CSLFetchNameValue(papszHdrLines, szLongName) != nullptr)
393
0
        {
394
0
            GDALInitGCPs(1, pasGCPList + nGCPCount);
395
396
0
            CPLFree(pasGCPList[nGCPCount].pszId);
397
398
0
            pasGCPList[nGCPCount].pszId = CPLStrdup(pszBase);
399
400
0
            pasGCPList[nGCPCount].dfGCPX =
401
0
                CPLAtof(CSLFetchNameValue(papszHdrLines, szLongName));
402
0
            pasGCPList[nGCPCount].dfGCPY =
403
0
                CPLAtof(CSLFetchNameValue(papszHdrLines, szLatName));
404
0
            pasGCPList[nGCPCount].dfGCPZ = 0.0;
405
406
0
            pasGCPList[nGCPCount].dfGCPPixel = dfRasterX;
407
0
            pasGCPList[nGCPCount].dfGCPLine = dfRasterY;
408
409
0
            nGCPCount++;
410
0
        }
411
0
    }
412
413
    /* -------------------------------------------------------------------- */
414
    /*      Collect standalone GCPs.  They look like:                       */
415
    /*                                                                      */
416
    /*      GCPn = row, col, lat, long                                      */
417
    /*      GCP1 = 1, 1, 45.0, -75.0                                        */
418
    /* -------------------------------------------------------------------- */
419
0
    for (int i = 0; i < NUM_GCPS; i++)
420
0
    {
421
0
        char szName[25] = {'\0'};
422
0
        snprintf(szName, sizeof(szName), "GCP%d", i + 1);
423
0
        if (CSLFetchNameValue(papszHdrLines, szName) == nullptr)
424
0
            continue;
425
426
0
        char **papszTokens = CSLTokenizeStringComplex(
427
0
            CSLFetchNameValue(papszHdrLines, szName), ",", FALSE, FALSE);
428
0
        if (CSLCount(papszTokens) == 4)
429
0
        {
430
0
            GDALInitGCPs(1, pasGCPList + nGCPCount);
431
432
0
            CPLFree(pasGCPList[nGCPCount].pszId);
433
0
            pasGCPList[nGCPCount].pszId = CPLStrdup(szName);
434
435
0
            pasGCPList[nGCPCount].dfGCPX = CPLAtof(papszTokens[3]);
436
0
            pasGCPList[nGCPCount].dfGCPY = CPLAtof(papszTokens[2]);
437
0
            pasGCPList[nGCPCount].dfGCPZ = 0.0;
438
0
            pasGCPList[nGCPCount].dfGCPPixel = CPLAtof(papszTokens[1]) + 0.5;
439
0
            pasGCPList[nGCPCount].dfGCPLine = CPLAtof(papszTokens[0]) + 0.5;
440
441
0
            nGCPCount++;
442
0
        }
443
444
0
        CSLDestroy(papszTokens);
445
0
    }
446
0
}
447
448
/************************************************************************/
449
/*                        ScanForProjectionInfo                         */
450
/************************************************************************/
451
452
void MFFDataset::ScanForProjectionInfo()
453
0
{
454
0
    const char *pszProjName =
455
0
        CSLFetchNameValue(papszHdrLines, "PROJECTION_NAME");
456
0
    const char *pszOriginLong =
457
0
        CSLFetchNameValue(papszHdrLines, "PROJECTION_ORIGIN_LONGITUDE");
458
0
    const char *pszSpheroidName =
459
0
        CSLFetchNameValue(papszHdrLines, "SPHEROID_NAME");
460
461
0
    if (pszProjName == nullptr)
462
0
    {
463
0
        m_oSRS.Clear();
464
0
        m_oGCPSRS.Clear();
465
0
        return;
466
0
    }
467
0
    else if ((!EQUAL(pszProjName, "utm")) && (!EQUAL(pszProjName, "ll")))
468
0
    {
469
0
        CPLError(CE_Warning, CPLE_AppDefined,
470
0
                 "Only utm and lat/long projections are currently supported.");
471
0
        m_oSRS.Clear();
472
0
        m_oGCPSRS.Clear();
473
0
        return;
474
0
    }
475
0
    MFFSpheroidList *mffEllipsoids = new MFFSpheroidList;
476
477
0
    OGRSpatialReference oProj;
478
0
    oProj.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
479
0
    if (EQUAL(pszProjName, "utm"))
480
0
    {
481
0
        int nZone;
482
483
0
        if (pszOriginLong == nullptr)
484
0
        {
485
            // If origin not specified, assume 0.0.
486
0
            CPLError(
487
0
                CE_Warning, CPLE_AppDefined,
488
0
                "No projection origin longitude specified.  Assuming 0.0.");
489
0
            nZone = 31;
490
0
        }
491
0
        else
492
0
            nZone = 31 + static_cast<int>(floor(CPLAtof(pszOriginLong) / 6.0));
493
494
0
        if (nGCPCount >= 5 && pasGCPList[4].dfGCPY < 0)
495
0
            oProj.SetUTM(nZone, 0);
496
0
        else
497
0
            oProj.SetUTM(nZone, 1);
498
499
0
        if (pszOriginLong != nullptr)
500
0
            oProj.SetProjParm(SRS_PP_CENTRAL_MERIDIAN, CPLAtof(pszOriginLong));
501
0
    }
502
503
0
    OGRSpatialReference oLL;
504
0
    oLL.SetAxisMappingStrategy(OAMS_TRADITIONAL_GIS_ORDER);
505
0
    if (pszOriginLong != nullptr)
506
0
        oLL.SetProjParm(SRS_PP_LONGITUDE_OF_ORIGIN, CPLAtof(pszOriginLong));
507
508
0
    if (pszSpheroidName == nullptr)
509
0
    {
510
0
        CPLError(CE_Warning, CPLE_AppDefined,
511
0
                 "Unspecified ellipsoid.  Using wgs-84 parameters.");
512
513
0
        oProj.SetWellKnownGeogCS("WGS84");
514
0
        oLL.SetWellKnownGeogCS("WGS84");
515
0
    }
516
0
    else
517
0
    {
518
0
        if (mffEllipsoids->SpheroidInList(pszSpheroidName))
519
0
        {
520
0
            oProj.SetGeogCS(
521
0
                "unknown", "unknown", pszSpheroidName,
522
0
                mffEllipsoids->GetSpheroidEqRadius(pszSpheroidName),
523
0
                mffEllipsoids->GetSpheroidInverseFlattening(pszSpheroidName));
524
0
            oLL.SetGeogCS(
525
0
                "unknown", "unknown", pszSpheroidName,
526
0
                mffEllipsoids->GetSpheroidEqRadius(pszSpheroidName),
527
0
                mffEllipsoids->GetSpheroidInverseFlattening(pszSpheroidName));
528
0
        }
529
0
        else if (EQUAL(pszSpheroidName, "USER_DEFINED"))
530
0
        {
531
0
            const char *pszSpheroidEqRadius =
532
0
                CSLFetchNameValue(papszHdrLines, "SPHEROID_EQUATORIAL_RADIUS");
533
0
            const char *pszSpheroidPolarRadius =
534
0
                CSLFetchNameValue(papszHdrLines, "SPHEROID_POLAR_RADIUS");
535
0
            if ((pszSpheroidEqRadius != nullptr) &&
536
0
                (pszSpheroidPolarRadius != nullptr))
537
0
            {
538
0
                const double eq_radius = CPLAtof(pszSpheroidEqRadius);
539
0
                const double polar_radius = CPLAtof(pszSpheroidPolarRadius);
540
0
                oProj.SetGeogCS("unknown", "unknown", "unknown", eq_radius,
541
0
                                eq_radius / (eq_radius - polar_radius));
542
0
                oLL.SetGeogCS("unknown", "unknown", "unknown", eq_radius,
543
0
                              eq_radius / (eq_radius - polar_radius));
544
0
            }
545
0
            else
546
0
            {
547
0
                CPLError(CE_Warning, CPLE_AppDefined,
548
0
                         "Radii not specified for user-defined ellipsoid. "
549
0
                         "Using wgs-84 parameters.");
550
0
                oProj.SetWellKnownGeogCS("WGS84");
551
0
                oLL.SetWellKnownGeogCS("WGS84");
552
0
            }
553
0
        }
554
0
        else
555
0
        {
556
0
            CPLError(CE_Warning, CPLE_AppDefined,
557
0
                     "Unrecognized ellipsoid.  Using wgs-84 parameters.");
558
0
            oProj.SetWellKnownGeogCS("WGS84");
559
0
            oLL.SetWellKnownGeogCS("WGS84");
560
0
        }
561
0
    }
562
563
    /* If a geotransform is sufficient to represent the GCP's (i.e. each */
564
    /* estimated gcp is within 0.25*pixel size of the actual value- this */
565
    /* is the test applied by GDALGCPsToGeoTransform), store the         */
566
    /* geotransform.                                                     */
567
0
    bool transform_ok = false;
568
569
0
    if (EQUAL(pszProjName, "LL"))
570
0
    {
571
0
        transform_ok = CPL_TO_BOOL(
572
0
            GDALGCPsToGeoTransform(nGCPCount, pasGCPList, m_gt.data(), 0));
573
0
    }
574
0
    else
575
0
    {
576
0
        OGRCoordinateTransformation *poTransform =
577
0
            OGRCreateCoordinateTransformation(&oLL, &oProj);
578
0
        bool bSuccess = true;
579
0
        if (poTransform == nullptr)
580
0
        {
581
0
            CPLErrorReset();
582
0
            bSuccess = FALSE;
583
0
        }
584
585
0
        double *dfPrjX =
586
0
            static_cast<double *>(CPLMalloc(nGCPCount * sizeof(double)));
587
0
        double *dfPrjY =
588
0
            static_cast<double *>(CPLMalloc(nGCPCount * sizeof(double)));
589
590
0
        for (int gcp_index = 0; gcp_index < nGCPCount; gcp_index++)
591
0
        {
592
0
            dfPrjX[gcp_index] = pasGCPList[gcp_index].dfGCPX;
593
0
            dfPrjY[gcp_index] = pasGCPList[gcp_index].dfGCPY;
594
595
0
            if (bSuccess && !poTransform->Transform(1, &(dfPrjX[gcp_index]),
596
0
                                                    &(dfPrjY[gcp_index])))
597
0
                bSuccess = FALSE;
598
0
        }
599
600
0
        if (bSuccess)
601
0
        {
602
0
            for (int gcp_index = 0; gcp_index < nGCPCount; gcp_index++)
603
0
            {
604
0
                pasGCPList[gcp_index].dfGCPX = dfPrjX[gcp_index];
605
0
                pasGCPList[gcp_index].dfGCPY = dfPrjY[gcp_index];
606
0
            }
607
0
            transform_ok = CPL_TO_BOOL(
608
0
                GDALGCPsToGeoTransform(nGCPCount, pasGCPList, m_gt.data(), 0));
609
0
        }
610
611
0
        if (poTransform)
612
0
            delete poTransform;
613
614
0
        CPLFree(dfPrjX);
615
0
        CPLFree(dfPrjY);
616
0
    }
617
618
0
    m_oSRS = oProj;
619
0
    m_oGCPSRS = std::move(oProj);
620
621
0
    if (!transform_ok)
622
0
    {
623
        /* transform is sufficient in some cases (slant range, standalone gcps)
624
         */
625
0
        m_gt = GDALGeoTransform();
626
0
        m_oSRS.Clear();
627
0
    }
628
629
0
    delete mffEllipsoids;
630
0
}
631
632
/************************************************************************/
633
/*                                Open()                                */
634
/************************************************************************/
635
636
GDALDataset *MFFDataset::Open(GDALOpenInfo *poOpenInfo)
637
638
465k
{
639
    /* -------------------------------------------------------------------- */
640
    /*      We assume the user is pointing to the header file.              */
641
    /* -------------------------------------------------------------------- */
642
465k
    if (poOpenInfo->nHeaderBytes < 17 || poOpenInfo->fpL == nullptr)
643
379k
        return nullptr;
644
645
86.0k
    if (!poOpenInfo->IsExtensionEqualToCI("hdr"))
646
85.4k
        return nullptr;
647
648
    /* -------------------------------------------------------------------- */
649
    /*      Load the .hdr file, and compress white space out around the     */
650
    /*      equal sign.                                                     */
651
    /* -------------------------------------------------------------------- */
652
635
    char **papszHdrLines = CSLLoad(poOpenInfo->pszFilename);
653
635
    if (papszHdrLines == nullptr)
654
0
        return nullptr;
655
656
    // Remove spaces.  e.g.
657
    // SPHEROID_NAME = CLARKE_1866 -> SPHEROID_NAME=CLARKE_1866
658
246k
    for (int i = 0; papszHdrLines[i] != nullptr; i++)
659
246k
    {
660
246k
        int iDst = 0;
661
246k
        char *pszLine = papszHdrLines[i];
662
663
6.22M
        for (int iSrc = 0; pszLine[iSrc] != '\0'; iSrc++)
664
5.97M
        {
665
5.97M
            if (pszLine[iSrc] != ' ')
666
5.24M
            {
667
5.24M
                pszLine[iDst++] = pszLine[iSrc];
668
5.24M
            }
669
5.97M
        }
670
246k
        pszLine[iDst] = '\0';
671
246k
    }
672
673
    /* -------------------------------------------------------------------- */
674
    /*      Verify it is an MFF file.                                       */
675
    /* -------------------------------------------------------------------- */
676
635
    if (CSLFetchNameValue(papszHdrLines, "IMAGE_FILE_FORMAT") != nullptr &&
677
0
        !EQUAL(CSLFetchNameValue(papszHdrLines, "IMAGE_FILE_FORMAT"), "MFF"))
678
0
    {
679
0
        CSLDestroy(papszHdrLines);
680
0
        return nullptr;
681
0
    }
682
683
635
    if ((CSLFetchNameValue(papszHdrLines, "IMAGE_LINES") == nullptr ||
684
0
         CSLFetchNameValue(papszHdrLines, "LINE_SAMPLES") == nullptr) &&
685
635
        (CSLFetchNameValue(papszHdrLines, "no_rows") == nullptr ||
686
0
         CSLFetchNameValue(papszHdrLines, "no_columns") == nullptr))
687
635
    {
688
635
        CSLDestroy(papszHdrLines);
689
635
        return nullptr;
690
635
    }
691
692
    /* -------------------------------------------------------------------- */
693
    /*      Create a corresponding GDALDataset.                             */
694
    /* -------------------------------------------------------------------- */
695
0
    auto poDS = std::make_unique<MFFDataset>();
696
697
0
    poDS->papszHdrLines = papszHdrLines;
698
699
0
    poDS->eAccess = poOpenInfo->eAccess;
700
701
    /* -------------------------------------------------------------------- */
702
    /*      Set some dataset wide information.                              */
703
    /* -------------------------------------------------------------------- */
704
0
    if (CSLFetchNameValue(papszHdrLines, "no_rows") != nullptr &&
705
0
        CSLFetchNameValue(papszHdrLines, "no_columns") != nullptr)
706
0
    {
707
0
        poDS->nRasterXSize =
708
0
            atoi(CSLFetchNameValue(papszHdrLines, "no_columns"));
709
0
        poDS->nRasterYSize = atoi(CSLFetchNameValue(papszHdrLines, "no_rows"));
710
0
    }
711
0
    else
712
0
    {
713
0
        poDS->nRasterXSize =
714
0
            atoi(CSLFetchNameValue(papszHdrLines, "LINE_SAMPLES"));
715
0
        poDS->nRasterYSize =
716
0
            atoi(CSLFetchNameValue(papszHdrLines, "IMAGE_LINES"));
717
0
    }
718
719
0
    if (!GDALCheckDatasetDimensions(poDS->nRasterXSize, poDS->nRasterYSize))
720
0
    {
721
0
        return nullptr;
722
0
    }
723
724
0
    RawRasterBand::ByteOrder eByteOrder = RawRasterBand::NATIVE_BYTE_ORDER;
725
726
0
    const char *pszByteOrder = CSLFetchNameValue(papszHdrLines, "BYTE_ORDER");
727
0
    if (pszByteOrder)
728
0
    {
729
0
        eByteOrder = EQUAL(pszByteOrder, "LSB")
730
0
                         ? RawRasterBand::ByteOrder::ORDER_LITTLE_ENDIAN
731
0
                         : RawRasterBand::ByteOrder::ORDER_BIG_ENDIAN;
732
0
    }
733
734
    /* -------------------------------------------------------------------- */
735
    /*      Get some information specific to APP tiled files.               */
736
    /* -------------------------------------------------------------------- */
737
0
    int nTileXSize = 0;
738
0
    int nTileYSize = 0;
739
0
    const char *pszRefinedType = CSLFetchNameValue(papszHdrLines, "type");
740
0
    const bool bTiled = CSLFetchNameValue(papszHdrLines, "no_rows") != nullptr;
741
742
0
    if (bTiled)
743
0
    {
744
0
        if (CSLFetchNameValue(papszHdrLines, "tile_size_rows"))
745
0
            nTileYSize =
746
0
                atoi(CSLFetchNameValue(papszHdrLines, "tile_size_rows"));
747
0
        if (CSLFetchNameValue(papszHdrLines, "tile_size_columns"))
748
0
            nTileXSize =
749
0
                atoi(CSLFetchNameValue(papszHdrLines, "tile_size_columns"));
750
751
0
        if (nTileXSize <= 0 || nTileYSize <= 0 ||
752
0
            poDS->nRasterXSize - 1 > INT_MAX - nTileXSize ||
753
0
            poDS->nRasterYSize - 1 > INT_MAX - nTileYSize)
754
0
        {
755
0
            return nullptr;
756
0
        }
757
0
    }
758
759
    /* -------------------------------------------------------------------- */
760
    /*      Read the directory to find matching band files.                 */
761
    /* -------------------------------------------------------------------- */
762
0
    const std::string osTargetPath = CPLGetPathSafe(poOpenInfo->pszFilename);
763
0
    const std::string osTargetBase =
764
0
        CPLGetBasenameSafe(poOpenInfo->pszFilename);
765
0
    const CPLStringList aosDirFiles(
766
0
        VSIReadDir(CPLGetPathSafe(poOpenInfo->pszFilename).c_str()));
767
0
    if (aosDirFiles.empty())
768
0
    {
769
0
        return nullptr;
770
0
    }
771
772
0
    int nSkipped = 0;
773
0
    for (int nRawBand = 0; true; nRawBand++)
774
0
    {
775
        /* Find the next raw band file. */
776
777
0
        int i = 0;  // Used after for.
778
0
        for (; i < aosDirFiles.size(); i++)
779
0
        {
780
0
            if (!EQUAL(CPLGetBasenameSafe(aosDirFiles[i]).c_str(),
781
0
                       osTargetBase.c_str()))
782
0
                continue;
783
784
0
            const std::string osExtension = CPLGetExtensionSafe(aosDirFiles[i]);
785
0
            if (osExtension.size() >= 2 &&
786
0
                isdigit(static_cast<unsigned char>(osExtension[1])) &&
787
0
                atoi(osExtension.c_str() + 1) == nRawBand &&
788
0
                strchr("bBcCiIjJrRxXzZ", osExtension[0]) != nullptr)
789
0
                break;
790
0
        }
791
792
0
        if (i == aosDirFiles.size())
793
0
            break;
794
795
        /* open the file for required level of access */
796
0
        const std::string osRawFilename =
797
0
            CPLFormFilenameSafe(osTargetPath.c_str(), aosDirFiles[i], nullptr);
798
799
0
        VSILFILE *fpRaw = nullptr;
800
0
        if (poOpenInfo->eAccess == GA_Update)
801
0
            fpRaw = VSIFOpenL(osRawFilename.c_str(), "rb+");
802
0
        else
803
0
            fpRaw = VSIFOpenL(osRawFilename.c_str(), "rb");
804
805
0
        if (fpRaw == nullptr)
806
0
        {
807
0
            CPLError(CE_Warning, CPLE_OpenFailed,
808
0
                     "Unable to open %s ... skipping.", osRawFilename.c_str());
809
0
            nSkipped++;
810
0
            continue;
811
0
        }
812
0
        poDS->m_papszFileList =
813
0
            CSLAddString(poDS->m_papszFileList, osRawFilename.c_str());
814
815
0
        GDALDataType eDataType = GDT_Unknown;
816
0
        const std::string osExt = CPLGetExtensionSafe(aosDirFiles[i]);
817
0
        if (pszRefinedType != nullptr)
818
0
        {
819
0
            if (EQUAL(pszRefinedType, "C*4"))
820
0
                eDataType = GDT_CFloat32;
821
0
            else if (EQUAL(pszRefinedType, "C*8"))
822
0
                eDataType = GDT_CFloat64;
823
0
            else if (EQUAL(pszRefinedType, "R*4"))
824
0
                eDataType = GDT_Float32;
825
0
            else if (EQUAL(pszRefinedType, "R*8"))
826
0
                eDataType = GDT_Float64;
827
0
            else if (EQUAL(pszRefinedType, "I*1"))
828
0
                eDataType = GDT_UInt8;
829
0
            else if (EQUAL(pszRefinedType, "I*2"))
830
0
                eDataType = GDT_Int16;
831
0
            else if (EQUAL(pszRefinedType, "I*4"))
832
0
                eDataType = GDT_Int32;
833
0
            else if (EQUAL(pszRefinedType, "U*2"))
834
0
                eDataType = GDT_UInt16;
835
0
            else if (EQUAL(pszRefinedType, "U*4"))
836
0
                eDataType = GDT_UInt32;
837
0
            else if (EQUAL(pszRefinedType, "J*1"))
838
0
            {
839
0
                CPLError(
840
0
                    CE_Warning, CPLE_OpenFailed,
841
0
                    "Unable to open band %d because type J*1 is not handled. "
842
0
                    "Skipping.",
843
0
                    nRawBand + 1);
844
0
                nSkipped++;
845
0
                CPL_IGNORE_RET_VAL(VSIFCloseL(fpRaw));
846
0
                continue;  // Does not support 1 byte complex.
847
0
            }
848
0
            else if (EQUAL(pszRefinedType, "J*2"))
849
0
                eDataType = GDT_CInt16;
850
0
            else if (EQUAL(pszRefinedType, "K*4"))
851
0
                eDataType = GDT_CInt32;
852
0
            else
853
0
            {
854
0
                CPLError(
855
0
                    CE_Warning, CPLE_OpenFailed,
856
0
                    "Unable to open band %d because type %s is not handled. "
857
0
                    "Skipping.\n",
858
0
                    nRawBand + 1, pszRefinedType);
859
0
                nSkipped++;
860
0
                CPL_IGNORE_RET_VAL(VSIFCloseL(fpRaw));
861
0
                continue;
862
0
            }
863
0
        }
864
0
        else if (STARTS_WITH_CI(osExt.c_str(), "b"))
865
0
        {
866
0
            eDataType = GDT_UInt8;
867
0
        }
868
0
        else if (STARTS_WITH_CI(osExt.c_str(), "i"))
869
0
        {
870
0
            eDataType = GDT_UInt16;
871
0
        }
872
0
        else if (STARTS_WITH_CI(osExt.c_str(), "j"))
873
0
        {
874
0
            eDataType = GDT_CInt16;
875
0
        }
876
0
        else if (STARTS_WITH_CI(osExt.c_str(), "r"))
877
0
        {
878
0
            eDataType = GDT_Float32;
879
0
        }
880
0
        else if (STARTS_WITH_CI(osExt.c_str(), "x"))
881
0
        {
882
0
            eDataType = GDT_CFloat32;
883
0
        }
884
0
        else
885
0
        {
886
0
            CPLError(CE_Warning, CPLE_OpenFailed,
887
0
                     "Unable to open band %d because extension %s is not "
888
0
                     "handled.  Skipping.",
889
0
                     nRawBand + 1, osExt.c_str());
890
0
            nSkipped++;
891
0
            CPL_IGNORE_RET_VAL(VSIFCloseL(fpRaw));
892
0
            continue;
893
0
        }
894
895
0
        const int nBand = poDS->GetRasterCount() + 1;
896
897
0
        const int nPixelOffset = GDALGetDataTypeSizeBytes(eDataType);
898
0
        std::unique_ptr<GDALRasterBand> poBand;
899
900
0
        if (bTiled)
901
0
        {
902
0
            if (nTileXSize > INT_MAX / nTileYSize / nPixelOffset)
903
0
            {
904
0
                CPLError(CE_Failure, CPLE_AppDefined, "Too large tile");
905
0
                CPL_IGNORE_RET_VAL(VSIFCloseL(fpRaw));
906
0
                return nullptr;
907
0
            }
908
909
0
            poBand = std::make_unique<MFFTiledBand>(poDS.get(), nBand, fpRaw,
910
0
                                                    nTileXSize, nTileYSize,
911
0
                                                    eDataType, eByteOrder);
912
0
        }
913
0
        else
914
0
        {
915
0
            if (nPixelOffset != 0 &&
916
0
                poDS->GetRasterXSize() > INT_MAX / nPixelOffset)
917
0
            {
918
0
                CPLError(CE_Warning, CPLE_AppDefined,
919
0
                         "Int overflow occurred... skipping");
920
0
                nSkipped++;
921
0
                CPL_IGNORE_RET_VAL(VSIFCloseL(fpRaw));
922
0
                continue;
923
0
            }
924
925
0
            poBand = RawRasterBand::Create(
926
0
                poDS.get(), nBand, fpRaw, 0, nPixelOffset,
927
0
                nPixelOffset * poDS->GetRasterXSize(), eDataType, eByteOrder,
928
0
                RawRasterBand::OwnFP::YES);
929
0
        }
930
931
0
        poDS->SetBand(nBand, std::move(poBand));
932
0
    }
933
934
    /* -------------------------------------------------------------------- */
935
    /*      Check if we have bands.                                         */
936
    /* -------------------------------------------------------------------- */
937
0
    if (poDS->GetRasterCount() == 0)
938
0
    {
939
0
        if (nSkipped > 0 && poOpenInfo->eAccess)
940
0
        {
941
0
            CPLError(CE_Failure, CPLE_OpenFailed,
942
0
                     "Failed to open %d files that were apparently bands.  "
943
0
                     "Perhaps this dataset is readonly?",
944
0
                     nSkipped);
945
0
            return nullptr;
946
0
        }
947
0
        else
948
0
        {
949
0
            CPLError(CE_Failure, CPLE_OpenFailed,
950
0
                     "MFF header file read successfully, but no bands "
951
0
                     "were successfully found and opened.");
952
0
            return nullptr;
953
0
        }
954
0
    }
955
956
    /* -------------------------------------------------------------------- */
957
    /*      Set all information from the .hdr that isn't well know to be    */
958
    /*      metadata.                                                       */
959
    /* -------------------------------------------------------------------- */
960
0
    for (int i = 0; papszHdrLines[i] != nullptr; i++)
961
0
    {
962
0
        char *pszName = nullptr;
963
964
0
        const char *pszValue = CPLParseNameValue(papszHdrLines[i], &pszName);
965
0
        if (pszName == nullptr || pszValue == nullptr)
966
0
            continue;
967
968
0
        if (!EQUAL(pszName, "END") && !EQUAL(pszName, "FILE_TYPE") &&
969
0
            !EQUAL(pszName, "BYTE_ORDER") && !EQUAL(pszName, "no_columns") &&
970
0
            !EQUAL(pszName, "no_rows") && !EQUAL(pszName, "type") &&
971
0
            !EQUAL(pszName, "tile_size_rows") &&
972
0
            !EQUAL(pszName, "tile_size_columns") &&
973
0
            !EQUAL(pszName, "IMAGE_FILE_FORMAT") &&
974
0
            !EQUAL(pszName, "IMAGE_LINES") && !EQUAL(pszName, "LINE_SAMPLES"))
975
0
        {
976
0
            poDS->SetMetadataItem(pszName, pszValue);
977
0
        }
978
979
0
        CPLFree(pszName);
980
0
    }
981
982
    /* -------------------------------------------------------------------- */
983
    /*      Any GCPs in header file?                                        */
984
    /* -------------------------------------------------------------------- */
985
0
    poDS->ScanForGCPs();
986
0
    poDS->ScanForProjectionInfo();
987
0
    if (poDS->nGCPCount == 0)
988
0
        poDS->m_oGCPSRS.Clear();
989
990
    /* -------------------------------------------------------------------- */
991
    /*      Initialize any PAM information.                                 */
992
    /* -------------------------------------------------------------------- */
993
0
    poDS->SetDescription(poOpenInfo->pszFilename);
994
0
    poDS->TryLoadXML();
995
996
    /* -------------------------------------------------------------------- */
997
    /*      Check for overviews.                                            */
998
    /* -------------------------------------------------------------------- */
999
0
    poDS->oOvManager.Initialize(poDS.get(), poOpenInfo->pszFilename);
1000
1001
0
    return poDS.release();
1002
0
}
1003
1004
/************************************************************************/
1005
/*                          GDALRegister_MFF()                          */
1006
/************************************************************************/
1007
1008
void GDALRegister_MFF()
1009
1010
22
{
1011
22
    if (GDALGetDriverByName("MFF") != nullptr)
1012
0
        return;
1013
1014
22
    GDALDriver *poDriver = new GDALDriver();
1015
1016
22
    poDriver->SetDescription("MFF");
1017
22
    poDriver->SetMetadataItem(GDAL_DCAP_RASTER, "YES");
1018
22
    poDriver->SetMetadataItem(GDAL_DMD_LONGNAME, "Vexcel MFF Raster");
1019
22
    poDriver->SetMetadataItem(GDAL_DMD_HELPTOPIC, "drivers/raster/mff.html");
1020
22
    poDriver->SetMetadataItem(GDAL_DMD_EXTENSION, "hdr");
1021
22
    poDriver->SetMetadataItem(GDAL_DCAP_VIRTUALIO, "YES");
1022
1023
22
    poDriver->pfnOpen = MFFDataset::Open;
1024
1025
22
    GetGDALDriverManager()->RegisterDriver(poDriver);
1026
22
}